gpst: Move code out of print_entry() into the new pause_entry()
[gpstools.git] / gpst
blobef4bb0cf87a77774e750a1b40ae7bb450f78bdf5
1 #!/usr/bin/perl -w
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 Getopt::Long;
16 use Time::Local qw { timegm_nocheck };
18 BEGIN {
19 push(@INC, "$ENV{'HOME'}/bin/src/gpstools");
22 use GPST;
23 use GPSTdate;
24 use GPSTdebug;
25 use GPSTgeo;
26 use GPSTxml;
28 $| = 1;
30 our $Debug = 0;
32 our %Opt = (
33 # Initial values for command line arguments {{{
35 'chronology' => 0,
36 'create-breaks' => 0,
37 'debug' => 0,
38 'double-y-scale' => 0,
39 'epoch' => 0,
40 'fix' => 0,
41 'from-date' => "",
42 'help' => 0,
43 'inside' => 0,
44 'output-format' => "gpsml",
45 'outside' => 0,
46 'pos1' => "",
47 'pos2' => "",
48 'require' => "",
49 'round' => "",
50 'save-to-file' => "\n", # \n = undefined, it’s banned in filenames anyway.
51 'short-date' => 0,
52 'skip-dups' => 0,
53 'strip-whitespace' => 0,
54 'time-shift' => 0,
55 'undefined' => "",
56 'verbose' => 0,
57 'version' => 0,
59 # }}}
62 our $progname = $0;
63 $progname =~ s/^.*\/(.*?)$/$1/;
64 our $VERSION = "0.00";
66 Getopt::Long::Configure("bundling");
67 GetOptions(
68 # Command line options {{{
70 "chronology" => \$Opt{'chronology'},
71 "create-breaks|t" => \$Opt{'create-breaks'},
72 "debug" => \$Opt{'debug'},
73 "double-y-scale|y" => \$Opt{'double-y-scale'},
74 "epoch|e" => \$Opt{'epoch'},
75 "fix" => \$Opt{'fix'},
76 "from-date=s" => \$Opt{'from-date'},
77 "help|h" => \$Opt{'help'},
78 "inside" => \$Opt{'inside'},
79 "output-format|o=s" => \$Opt{'output-format'},
80 "outside" => \$Opt{'outside'},
81 "pos1=s" => \$Opt{'pos1'},
82 "pos2=s" => \$Opt{'pos2'},
83 "require|r=s" => \$Opt{'require'},
84 "round|R=s" => \$Opt{'round'},
85 "save-to-file|S=s" => \$Opt{'save-to-file'},
86 "short-date|s" => \$Opt{'short-date'},
87 "skip-dups|d" => \$Opt{'skip-dups'},
88 "strip-whitespace|w" => \$Opt{'strip-whitespace'},
89 "time-shift|T=i" => \$Opt{'time-shift'},
90 "undefined|n=s" => \$Opt{'undefined'},
91 "verbose|v+" => \$Opt{'verbose'},
92 "version" => \$Opt{'version'},
94 # }}}
95 ) || die("$progname: Option error. Use -h for help.\n");
97 my %Dat;
99 my $PAUSE_LIMIT = 2 * 60; # Antall sekunder mellom to punkter det må til før en move legges inn.
100 my $Udef = "?";
101 my $DIGIT = '[0-9\.\-\+]'; # Used in regexps
102 $GPST::Spc = $Opt{'strip-whitespace'} ? "" : " ";
103 my $Spc = $GPST::Spc; # FIXME
104 my $found_move = 0; # Settes til 1 hvis en /^# move$/ blir funnet.
105 my $first_time = 0;
106 my $last_time = 0;
107 my ($last_lon, $last_lat, $last_line) =
108 ( 1000, 1000, ""); # Vi kan jo teoretisk sett være i Greenwich eller på ekvator
109 my ($lat1, $lon1, $lat2, $lon2) =
110 (-1000, -1000, 1000, 1000);
112 our %Cmd = (
113 'gpsbabel' => '/usr/local/bin/gpsbabel',
116 my %Poscount = ();
118 if ($Opt{'output-format'} =~ /^(gpx|pgtab)$/) {
119 $Opt{'require'} .= "p";
121 my %Req = (
122 'ele' => ($Opt{'require'} =~ /e/) ? 1 : 0,
123 'position' => ($Opt{'require'} =~ /p/) ? 1 : 0,
124 'time' => ($Opt{'require'} =~ /t/) ? 1 : 0,
126 $Opt{'require'} =~ /[^ept]/
127 && die("$0: Unknown flag in --require (-r) value\n");
129 $Opt{'debug'} && ($Debug = 1);
130 $Opt{'help'} && usage(0);
131 if ($Opt{'version'}) {
132 print_version();
133 exit(0);
136 if ($Opt{'pos1'} =~ /^($DIGIT+),($DIGIT+)$/) {
137 $lat1 = $1;
138 $lon1 = $2;
140 if ($Opt{'pos2'} =~ /^($DIGIT+),($DIGIT+)$/) {
141 $lat2 = $1;
142 $lon2 = $2;
144 if ($lat1 > $lat2) {
145 my $Tmp = $lat1;
146 $lat1 = $lat2;
147 $lat2 = $Tmp;
149 if ($lon1 > $lon2) {
150 my $Tmp = $lon1;
151 $lon1 = $lon2;
152 $lon2 = $Tmp;
155 if ($Opt{'epoch'} && $Opt{'short-date'}) {
156 die("$progname: Cannot mix the --epoch (-e) and --short-date (-s) options\n");
159 if ($Opt{'inside'} && $Opt{'outside'}) {
160 die("$progname: Cannot mix the --inside and --outside options\n");
163 # To avoid printing out extra "/> at the start of svg output:
164 my $svg_start_thing = "";
166 my %Round = ();
168 if (defined($Opt{'round'})) {
169 my $R = $Opt{'round'};
170 $R =~ s/([a-z]+)=(\d+)/($Round{$1}=$2, "")/eg;
173 length($Opt{'undefined'}) && ($Udef = $Opt{'undefined'});
175 $Opt{'save-to-file'} eq "\n" && print_header(*STDOUT);
177 my @first_lines;
178 my $xml_data;
179 my $data_line = "";
180 our $curr_file = "";
182 my $from_stdin = scalar(@ARGV) ? 0 : 1;
184 $from_stdin && push(@ARGV, "-");
186 for $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(CurrFP, "<$curr_file")) {
192 # {{{
193 while (<CurrFP>) {
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',
207 $Opt{'epoch'} && ($Dat{'date-format'} = "epoch");
208 $Opt{'short-date'} && ($Dat{'date-format'} = "short");
210 if ($Opt{'save-to-file'} ne "\n") {
211 push(@first_lines, $_);
213 s/^# error // && ($Dat{'error'} = "error");
214 s/^# ?// && ($Dat{'error'} = "desc");
215 $xml_data = "";
216 if (m#^<(e?tp)\b(.*?)>(.*?)</(e?tp)>\s*$#) {
217 # gpsml — The main storage format {{{
218 my ($Elem, $Props, $Data) =
219 ( $1, $2, $3);
220 my $err_str = ($Props =~ /\berr="(.*?)"/) ? $1 : "error";
221 $Elem eq "etp" && ($Dat{'error'} = $err_str);
222 my $Time = "";
223 $Data =~ m#<time>(.*?)</time># && ($Time = $1);
224 $Time =~ s{
225 (\d\d\d\d)-?(\d\d)-?(\d\d)[T ](\d\d):?(\d\d):?([\d\.]+?)Z
227 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
228 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
229 ( $1, $2, $3,
230 $4, $5, $6);
232 }ex;
233 $Data =~ m#<lat>($DIGIT*?)</lat># && ($Dat{'lat'} = $1);
234 $Data =~ m#<lon>($DIGIT*?)</lon># && ($Dat{'lon'} = $1);
235 $Data =~ m#<ele>($DIGIT*?)</ele># && ($Dat{'ele'} = $1);
236 $Data =~ m#<desc>(.*?)</desc># && ($Dat{'desc'} = $1);
237 print_entry(%Dat);
238 # }}}
239 } elsif (m#^<break\b.*?/>#) {
240 $found_move = 1;
241 } elsif (m#^<(title|pause)\b.*?>(.*?)</(title|pause)>#) {
242 $Dat{'what'} = $1;
243 $Dat{$1} = $2;
244 print_entry(%Dat);
245 } elsif (m#^<desc\b.*?>(.*$)#s) {
246 $Dat{'what'} = "desc";
247 my $Txt = $1;
248 until ($Txt =~ m#</desc>#s) {
249 $Txt .= <CurrFP>;
251 $Txt =~ s#^(.*)(</desc>.*$)#$1#s;
252 $Dat{'desc'} = $Txt;
253 print_entry(%Dat);
254 } elsif (/<gpx\b/) {
255 $xml_data = $_;
256 $xml_data .= join("", <CurrFP>);
257 if (!length($Opt{'output-format'})) {
258 $Opt{'output-format'} = "gpx";
259 print_header(*STDOUT);
261 read_xmlfile($xml_data);
262 last;
263 } elsif (/^move$/) {
264 $found_move = 1;
265 } elsif (m#^(\d+)\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
266 # CSV format, epoch style {{{
267 my ($ep_time, $lon_val, $lat_val, $Alt) =
268 ( $1, $2, $3, $4);
269 $Dat{'epoch'} = $ep_time;
270 ($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
271 $Dat{'day'}, $Dat{'month'}, $Dat{'year'},
272 $Dat{'wday'}, $Dat{'yday'}) = gmtime($ep_time);
273 $Dat{'month'}++; # Urgh Ⅰ
274 $Dat{'year'} += 1900; # Urgh Ⅱ
275 print_entry(%Dat);
276 # }}}
277 } elsif (
279 (\d\d\d\d)-?(\d\d)-?(\d\d)[T\ ](\d\d):?(\d\d):?(\d\d)Z?\t
280 ($DIGIT+)\t($DIGIT+)\t($DIGIT)
283 # CSV format, human-readable date format {{{
284 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
285 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
286 $Dat{'lon'}, $Dat{'lat'}, $Dat{'ele'}) =
287 ($1, $2, $3,
288 $4, $5, $6,
289 $7, $8, $9);
290 print_entry(%Dat);
291 # }}}
292 } elsif (/^Trackpoint\t/) {
293 # 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 {{{
295 # Trackpoint\t
296 # N60.41630 E5.31675\t
297 # 09.02.2006 20:24:37 (UTC)\t
298 # 13.6 m\t
299 # \t
300 # 93.9 m\t
301 # 00:00:06\t
302 # 56 kph\t
303 # 123° true
304 my $Orig = $_;
305 $Orig =~ s/[\r\n]+$//;
306 my ($Marker_f, $Position_f, $Time_f, $Alt_f, $Depth_f,
307 $Leglength_f, $Legtime_f, $Legspeed_f, $Legcourse_f) =
308 split(/\t/, $Orig .
309 # Nødløsning for å unngå at variabler
310 # blir udefinert.
311 "\t\t\t\t\t\t\t\t\t\t"
313 # D(join("",
314 # "Position_f=\"$Position_f\" \x7B\x7B\x7B\n",
315 # "Time_f=\"$Time_f\"\n",
316 # "Alt_f=\"$Alt_f\"\n",
317 # "Depth_f=\"$Depth_f\"\n",
318 # "Leglength_f=\"$Leglength_f\"\n",
319 # "Legtime_f=\"$Legtime_f\"\n",
320 # "Legspeed_f=\"$Legspeed_f\"\n",
321 # "Legcourse_f=\"$Legcourse_f\" \x7D\x7D\x7D\n",
322 # ));
323 my ($NS, $WE,
324 $Alt_unit,
325 $Leglength,
326 $Legtime_hour, $Legtime_min, $Legtime_sec,
327 $Legspeed, $Legspeed_unit,
328 $Legcourse
329 ) = ("", "", "", "", "", "", "", "", "", "", "", "", "",
330 "", "", "", "", "", "", "", "", "", "");
331 ($Position_f =~ /^(N|S)([\d\.]+) (W|E)([\d\.]+)/) &&
332 ($NS = $1, $Dat{'lat'} = $2, $WE = $3, $Dat{'lon'} = $4);
333 ($Time_f =~ /^(\d+)\.(\d+)\.(\d+) (\d+):(\d+):(\d+) \((.+?)\)/) &&
334 ($Dat{'day'} = $1, $Dat{'month'} = $2, $Dat{'year'} = $3,
335 $Dat{'hour'} = $4, $Dat{'min'} = $5, $Dat{'sec'} = $6);
336 ($Alt_f =~ /^($DIGIT+) (.*?)/) &&
337 ($Dat{'ele'} = $1, $Alt_unit = $2);
338 # D("ele = \"$Dat{'ele'}\"");
339 ($NS eq "S") && ($Dat{'lat'} = 0-$Dat{'lat'});
340 ($WE eq "W") && ($Dat{'lon'} = 0-$Dat{'lon'});
341 # MapSource in win xp writes YYYY, but YY in win98se.
343 defined($Dat{'year'})
344 && $Dat{'year'} =~ /\d/
345 && $Dat{'year'} < 1900
346 ) && ($Dat{'year'} += 2000);
347 print_entry(%Dat);
348 # }}}
349 } elsif (/^Track\t(.*?)\t/) {
350 $Dat{'title'} = txt_to_xml($1);
351 $Dat{'what'} = "title";
352 $found_move = 1;
353 print_entry(%Dat);
354 } elsif (
357 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)\t
358 (.+)\xB0(.+)'(.+)"\t
359 (.+)\xB0(.+)'(.+)"
362 # T 09/01/2002 11:51:26 60°23'36.3" 5°19'35.9" {{{
363 my ($lat_d, $lat_m, $lat_s, $lon_d, $lon_m, $lon_s);
364 ($Dat{'month'}, $Dat{'day'}, $Dat{'year'},
365 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
366 $lat_d, $lat_m, $lat_s,
367 $lon_d, $lon_m, $lon_s) =
368 ($1, $2, $3,
369 $4, $5, $6,
370 $7, $8, $9,
371 $10, $11, $12);
372 my $Flat = defined($Round{'lat'}) ? ".$Round{'lat'}" : "";
373 my $Flon = defined($Round{'lon'}) ? ".$Round{'lon'}" : "";
374 $Dat{'lat'} = sprintf("%${Flat}f",
375 1.0*($lat_d+($lat_m/60)+($lat_s/3600)));
376 $Dat{'lon'} = sprintf("%${Flon}f",
377 1.0*$lon_d+($lon_m/60)+($lon_s/3600));
378 print_entry(%Dat);
379 # }}}
380 } elsif (
382 1\ (\S+)\ (\S+)\ (\S+)\ (\S+)\x20
383 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)
386 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
387 ($Dat{'lat'}, $Dat{'lon'}, $Dat{'speed'},
388 $Dat{'unkn'},
389 $Dat{'month'}, $Dat{'day'}, $Dat{'year'},
390 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
391 ($1, $2, $3,
393 $5, $6, $7,
394 $8, $9, $10);
395 print_entry(%Dat);
396 # }}}
397 } elsif (/^
398 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
399 # Regexp {{{
400 (@) # @
401 (\d\d) # Year
402 (\d\d) # Month
403 (\d\d) # Day
404 (\d\d) # Hours
405 (\d\d) # Minutes
406 (\d\d) # Seconds
407 ([NS]) # N|S
408 (\d\d) # Latitude degree
409 (\d\d) # Latitude minute
410 (\d\d\d) # Latitude minute decimals
411 ([EW]) # E|W
412 (\d\d\d) # Longitude degree
413 (\d\d) # Longitude minute
414 (\d\d\d) # Longitude minute degree
415 (....) # Accurancy
416 (......) # Elevation
417 (...............)
418 # }}}
419 /x) {
420 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
421 $lon_degmin, $lon_mindec);
422 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'}, $Dat{'hour'},
423 $Dat{'min'}, $Dat{'sec'}, $NS, $lat_deg,
424 $lat_degmin, $lat_mindec, $EW,
425 $lon_deg, $lon_degmin, $lon_mindec,
426 $Dat{'accur'}, $Dat{'ele'}, $Dat{'unknown'}) =
427 ($2+2000, $3, $4, $5,
428 $6, $7, $8, $9,
429 $10, $11, $12,
430 $13, $14, $15,
431 $16, $17, $18);
432 my $ep_time = timegm_nocheck(
433 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
434 $Dat{'day'}, $Dat{'month'}-1, $Dat{'year'}
436 $last_time = $ep_time;
437 my $Flat = defined($Round{'lat'}) ? ".$Round{'lat'}" : "";
438 my $Flon = defined($Round{'lon'}) ? ".$Round{'lon'}" : "";
439 my $tmp_lon = sprintf(
440 "%${Flon}f",
441 $lon_deg +
442 $lon_degmin/60 +
443 $lon_mindec/60000);
444 my $tmp_lat = sprintf("%${Flat}f",
445 $lat_deg +
446 $lat_degmin/60 +
447 $lat_mindec/60000);
448 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
449 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
450 $Dat{'lat'} = $tmp_lat;
451 $Dat{'lon'} = $tmp_lon;
452 print_entry(%Dat);
453 # }}}
454 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(_{42})/) {
455 # @020721221336__________________________________________ {{{
456 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
457 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $Dat{'rest'}) =
458 ($2+2000, $3, $4,
459 $5, $6, $7, $8);
460 $Dat{'error'} = "nosignal";
461 print_entry(%Dat);
462 # }}}
463 } elsif (/^xmaplog /) {
464 # NOP
465 } elsif (/^$/) {
466 ($Opt{'output-format'} eq "csv")
467 && ($Opt{'save-to-file'} eq "\n")
468 && print("\n");
469 } elsif (/^Pause: /) {
470 # NOP, is here to cope with old files I’ve lying around.
471 } elsif ($Dat{'error'} eq "desc") {
472 my $Comment = $_;
473 if (defined($Comment)) {
474 $Comment =~ s/^\s*(.*?)\s*$/$1/;
475 if ($Opt{'output-format'} eq "gpsml") {
476 $Dat{'desc'} = txt_to_xml($Comment);
477 $Dat{'what'} = "desc";
478 print_entry(%Dat);
481 } else {
482 $Opt{'verbose'} && warn("Line $.: Unknown: \"$_\"\n");
485 # }}}
486 } else {
487 warn("$progname: $curr_file: Cannot open file for read: $!\n");
489 # }}}
492 print_footer(*STDOUT);
494 exit(0);
496 sub read_xmlfile {
497 # {{{
498 my $Txt = join("", @_);
499 $Txt =~ s/<!--(.*?)-->//gs;
500 $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
501 # }}}
504 sub print_gpx {
505 # {{{
506 my $Orig = shift;
507 my $Str = $Orig;
508 # D("print_xml_gps(\"$Orig\")\n");
509 $Str =~ s/<!--(.*?)-->//gs;
510 my $fromdate_str = "";
511 if ($Opt{'from-date'}) {
512 $fromdate_str = "date >= '$Opt{'from-date'}' AND ";
514 if ($Opt{'output-format'} =~ /^(pgwtab|pgwupd)$/) {
515 # {{{
516 $Str =~
518 <wpt\b(.*?)>(.*?)</wpt>
521 my $attr_wpt = $1;
522 my $el_wpt = $2;
523 my ($Lat, $Lon, $Name, $Ele, $Type, $Time, $Cmt, $Desc, $Src, $Sym) =
524 ('\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N');
526 $attr_wpt =~ /.*lat="($DIGIT+?)"/s &&
527 ($Lat = postgresql_copy_safe($1));
528 $attr_wpt =~ /.*lon="($DIGIT+?)"/s &&
529 ($Lon = postgresql_copy_safe($1));
530 $el_wpt =~ /.*<name\b(.*?)>(.*?)<\/name>/s &&
531 ($Name = postgresql_copy_safe(xml_to_txt($2)));
532 $el_wpt =~ /.*<ele\b(.*?)>(.*?)<\/ele>/s &&
533 ($Ele = postgresql_copy_safe(xml_to_txt($2)));
534 $el_wpt =~ /.*<type\b(.*?)>(.*?)<\/type>/s &&
535 ($Type = postgresql_copy_safe(xml_to_txt($2)));
536 $el_wpt =~ /.*<time\b(.*?)>(.*?)<\/time>/s &&
537 ($Time = postgresql_copy_safe(xml_to_txt($2)));
538 $el_wpt =~ /.*<cmt\b(.*?)>(.*?)<\/cmt>/s &&
539 ($Cmt = postgresql_copy_safe(xml_to_txt($2)));
540 $el_wpt =~ /.*<desc\b(.*?)>(.*?)<\/desc>/s &&
541 ($Desc = postgresql_copy_safe(xml_to_txt($2)));
542 $el_wpt =~ /.*<src\b(.*?)>(.*?)<\/src>/s &&
543 ($Src = postgresql_copy_safe(xml_to_txt($2)));
544 $el_wpt =~ /.*<sym\b(.*?)>(.*?)<\/sym>/s &&
545 ($Sym = postgresql_copy_safe(xml_to_txt($2)));
547 if (length($Opt{'round'})) {
548 if (defined($Round{'lat'}) && length($Lat)) {
549 ($Lat = 1.0 * sprintf("%.$Round{'lat'}f", $Lat));
551 if (defined($Round{'lon'}) && length($Lon)) {
552 ($Lon = 1.0 * sprintf("%.$Round{'lon'}f", $Lon));
554 if (defined($Round{'ele'}) && $Ele ne '\N') {
555 ($Ele = 1.0 * sprintf("%.$Round{'ele'}f", $Ele));
559 if ($Opt{'output-format'} eq "pgwtab") {
560 print(
561 join("\t",
562 "($Lat,$Lon)",
563 $Name,
564 $Ele,
565 $Type,
566 $Time,
567 $Cmt,
568 $Desc,
569 $Src,
570 $Sym
571 ) . "\n"
573 } elsif ($Opt{'output-format'} eq "pgwupd") {
574 $Name =~ s/'/''/gs;
575 print(join("\n",
576 "BEGIN;",
577 "$Spc${Spc}UPDATE logg SET name = clname(coor) " .
578 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
579 "$Spc${Spc}UPDATE logg SET dist = cldist(coor) " .
580 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
581 "COMMIT;"
582 ) . "\n");
585 }gsex;
586 # }}}
587 } else {
588 # {{{
589 $Str =~
591 <trk\b(.*?)>(.*?)</trk>
594 my $el_trk = $2;
595 $el_trk =~
597 <name\b(.*?)>(.*?)</name>
599 my %tmp_dat = ();
600 $tmp_dat{'title'} = $2;
601 $tmp_dat{'what'} = "title";
602 $tmp_dat{'error'} = "";
603 print_entry(%tmp_dat);
605 }sex;
606 $el_trk =~
608 <trkseg\b(.*?)>(.*?)</trkseg>
611 my $el_trkseg = $2;
612 $el_trkseg =~
614 <trkpt\b(.*?)>(.*?)</trkpt>
617 my ($attr_trkpt, $el_trkpt) =
618 ( $1, $2);
619 %Dat = (
620 'year' => '', 'month' => '', 'day' => '',
621 'hour' => '', 'min' => '', 'sec' => '',
622 'epoch' => '',
623 'date-format' => '',
624 'lat' => '', 'lon' => '',
625 'ele' => '',
626 'desc' => '',
627 'error' => "",
628 'what' => 'tp',
630 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon'} = $1);
631 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat'} = $1);
632 ($el_trkpt =~ m#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele'} = $1);
633 if (
634 $el_trkpt =~
636 <time>(\d\d\d\d)-?(\d\d)-?(\d\d)T
637 (\d\d):?(\d\d):?([\d\.]+)Z</time>
640 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
641 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
642 ($1, $2, $3, $4, $5, $6);
644 print_entry(%Dat);
646 }gsex;
647 $found_move = 1;
648 }gsex;
649 $found_move = 1;
650 }gsex;
651 # }}}
653 # }}}
656 sub print_header {
657 # {{{
658 local *OutFP = shift;
659 if ($Opt{'output-format'} eq "gpsml") {
660 print(OutFP join("",
661 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
662 "<gpsml>\n",
663 "<track>\n",
665 } elsif ($Opt{'output-format'} eq "gpstrans") {
666 print(OutFP "Format: DMS UTC Offset: 0.00 hrs " .
667 "Datum[100]: WGS 84\n");
668 } elsif ($Opt{'output-format'} eq "gpx") {
669 print(OutFP join("",
670 qq{<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n},
671 qq{<gpx\n},
672 qq{$Spc${Spc}version="1.1"\n},
673 qq{$Spc${Spc}creator="gpst - http://sunny256.github.com/gpstools/"\n},
674 qq{$Spc${Spc}xmlns="http://www.topografix.com/GPX/1/1"\n},
675 qq{$Spc${Spc}xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n},
676 qq{$Spc${Spc}xsi:schemaLocation="http://www.topografix.com/GPX/1/1 },
677 qq{http://www.topografix.com/GPX/1/1/gpx.xsd"\n},
678 qq{>\n},
679 qq{$Spc$Spc<trk>\n},
680 qq{$Spc$Spc$Spc$Spc<trkseg>\n},
682 } elsif ($Opt{'output-format'} eq "ps") {
683 print(OutFP ps_header(532, 6034, 533, 6040));
684 print(OutFP "*u\n");
685 } elsif ($Opt{'output-format'} eq "svg") {
686 print(OutFP join("",
687 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
688 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
689 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
690 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
691 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
692 "$Spc$Spc<title></title>\n",
693 "$Spc$Spc<desc></desc>\n",
696 # }}}
699 sub print_footer {
700 # Print footer {{{
701 local *OutFP = shift;
702 if ($Opt{'output-format'} eq "gpsml") {
703 print(OutFP join("",
704 "</track>\n",
705 "</gpsml>\n",
707 } elsif ($Opt{'output-format'} eq "gpx") {
708 print(OutFP join("",
709 "$Spc$Spc$Spc$Spc</trkseg>\n",
710 "$Spc$Spc</trk>\n",
711 "</gpx>\n",
713 } elsif ($Opt{'output-format'} eq "poscount") {
714 while (my ($l_name, $l_val) = each %Poscount) {
715 $l_name =~ /^(.+?),(.+?)$/
716 && print(OutFP "$1\t$2\t$l_val\n");
718 } elsif ($Opt{'output-format'} eq "ps") {
719 print(OutFP join("",
720 "*U\n",
721 "%%Trailer\n",
722 "%%EOF\n",
724 } elsif ($Opt{'output-format'} eq "svg") {
725 print(OutFP "\"/>\n</svg>\n");
727 # }}}
730 sub print_entry {
731 # Print a GPS entry with time, latitude, longitude and elevation in
732 # various formats
733 # {{{
734 my %Dat = @_;
735 defined($Dat{'desc'}) || ($Dat{'desc'} = "");
736 defined($Dat{'ele'}) || ($Dat{'ele'} = "");
737 defined($Dat{'lat'}) || ($Dat{'lat'} = "");
738 defined($Dat{'lon'}) || ($Dat{'lon'} = "");
739 defined($Dat{'year'}) || ($Dat{'year'} = "");
740 my $print_time = length($Dat{'year'}) ? 1 : 0;
741 my $print_pos;
742 if (!$Req{'position'} && $Opt{'output-format'} eq "gpsml") {
743 $print_pos = (length($Dat{'lat'}) || length($Dat{'lon'})) ? 1 : 0;
744 } else {
745 $print_pos = (length($Dat{'lat'}) && length($Dat{'lon'})) ? 1 : 0;
747 if (!$print_pos) {
748 $Dat{'lat'} = $Dat{'lon'} = "";
750 my $print_ele = length($Dat{'ele'}) ? 1 : 0;
751 my $print_desc = length($Dat{'desc'}) ? 1 : 0;
752 my $Line = "";
753 # D("print_entry(\"" . join("\", \"", @_) . "\");");
754 my $ep_time;
756 if (length($Opt{'round'})) {
757 for my $Tmp (qw{ lat lon ele }) {
758 if (defined($Round{$Tmp}) && length($Dat{$Tmp})) {
759 # D("Tmp = '$Tmp'");
760 ($Dat{$Tmp} = 1.0 * sprintf("%.$Round{$Tmp}f", $Dat{$Tmp}));
765 if ($Opt{'output-format'} eq "poscount") {
766 if (!length($Dat{'error'})) {
767 my $Name = "$Dat{'lon'},$Dat{'lat'}";
768 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
769 $Poscount{$Name}++;
771 return;
774 if ($print_time) {
775 $ep_time = timegm_nocheck(
776 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
777 $Dat{'day'}, $Dat{'month'} - 1, $Dat{'year'}
779 if ($Opt{'time-shift'}) {
780 # D("ep_time før: '$ep_time'");
781 $ep_time += $Opt{'time-shift'};
782 # D("ep_time etter: '$ep_time'");
783 ($Dat{'sec'}, $Dat{'min'},$Dat{'hour'}, $Dat{'day'},
784 $Dat{'month'}, $Dat{'year'}) = gmtime($ep_time);
785 $Dat{'year'} += 1900;
786 $Dat{'month'}++;
788 $Dat{'epoch'} = $ep_time;
789 $Dat{'year'} = sprintf("%04u", $Dat{'year'});
790 $Dat{'month'} = sprintf("%02u", $Dat{'month'});
791 $Dat{'day'} = sprintf("%02u", $Dat{'day'});
792 $Dat{'hour'} = sprintf("%02u", $Dat{'hour'});
793 $Dat{'min'} = sprintf("%02u", $Dat{'min'});
794 $Dat{'sec'} = sprintf("%02u", $Dat{'sec'});
795 if ($Opt{'chronology'}) {
796 if ($last_time > $ep_time && !length($Dat{'error'})) {
797 warn(sprintf(
798 "%s: $curr_file: \"%sZ\": Next date is %s in the past (%sZ)\n",
799 $progname, sec_to_string($last_time, "T"),
800 sec_to_readable($last_time-$ep_time),
801 sec_to_string($ep_time, "T")
803 # FIXME: Make --fix work with gpx.
804 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
805 $Dat{'error'} = "chrono";
807 } elsif ($last_time == $ep_time && !length($Dat{'error'})) {
808 warn(sprintf(
809 "%s: $curr_file: \"%sZ\": Duplicated time\n",
810 $progname, sec_to_string($last_time, "T")
812 # FIXME: Make --fix work with gpx.
813 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
814 $Dat{'error'} = "duptime";
818 } else {
819 $ep_time = 0;
820 $Dat{'year'} = 0;
821 $Dat{'month'} = 0;
822 $Dat{'day'} = 0;
823 $Dat{'hour'} = 0;
824 $Dat{'min'} = 0;
825 $Dat{'sec'} = 0;
828 if ($Opt{'save-to-file'} ne "\n") {
829 # {{{
830 $print_time || return;
831 my $base_name = "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
832 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z" .
833 "$Opt{'save-to-file'}";
834 my $file_name = $base_name;
835 if (-e $file_name) {
836 for (my $a = 1; (-e $file_name) && ($a < 1000); $a++) {
837 $file_name = "$base_name.dup_$a";
839 if (-e $file_name) {
840 die("$progname: $base_name: File already exists, and ran " .
841 "out of attempts to create unique file name\n");
843 if ($Opt{'verbose'}) {
844 warn("$progname: $base_name: File already exists, using " .
845 "unique name \"$file_name\" instead\n");
848 if (open(ToFP, ">", $file_name)) {
849 print_header(*ToFP);
850 print(ToFP (
851 $from_stdin
852 ? @first_lines
853 : ()),
854 (length($xml_data)
855 ? $xml_data
856 : <>)
857 ) || die("$progname: $file_name: Cannot write to file: $!\n");
858 print_footer(*ToFP);
859 close(ToFP);
860 if ($Opt{'output-format'} eq "gpsml") {
861 printf("<include>%s</include>\n",
862 txt_to_xml($file_name));
863 } elsif ($Opt{'output-format'} eq "gpx") {
864 printf("<!-- Saved unconverted data to \"%s\" -->\n",
865 txt_to_xml($file_name));
866 } else {
867 print("$progname: Saved unconverted data to \"$file_name\"\n");
869 exit 0;
870 } else {
871 die("$progname: $file_name: Cannot create file: $!\n");
873 # }}}
876 my $pause_len = 0;
877 my $do_print = 1;
879 if ($Dat{'what'} eq "tp") {
880 # {{{
881 if ($Opt{'require'}) {
882 $Req{'time'} && !$print_time && return;
883 $Req{'position'} && !$print_pos && return;
884 $Req{'ele'} && !$print_ele && return;
887 if ($Opt{'inside'} || $Opt{'outside'}) {
888 if (
889 ($Dat{'lat'} < $lat1) ||
890 ($Dat{'lat'} > $lat2) ||
891 ($Dat{'lon'} < $lon1) ||
892 ($Dat{'lon'} > $lon2)
894 $Opt{'inside'} && return;
895 } else {
896 $Opt{'outside'} && return;
900 if ($Opt{'output-format'} eq "ps") {
901 $Dat{'lon'} *= 100;
902 $Dat{'lat'} *= 100;
905 if (
906 $Opt{'skip-dups'}
907 && ($Dat{'lon'} eq $last_lon)
908 && ($Dat{'lat'} eq $last_lat)
910 if ($Opt{'output-format'} eq 'gpsml') {
911 $Dat{'error'} = "dup";
912 } else {
913 $do_print = 0;
915 } else {
916 $do_print = 1;
919 if (
920 $Opt{'create-breaks'}
921 && $ep_time-$last_time > $PAUSE_LIMIT
922 && $last_time
924 $pause_len = $ep_time-$last_time;
925 # D("pause_len set to '$pause_len'");
928 $Line .= pause_entry($pause_len, $ep_time, $last_time);
929 # }}}
932 if ($do_print) {
933 # Valid data was found, send to stdout {{{
934 unless ($first_time) {
935 $first_time = $ep_time;
937 if ($Opt{'double-y-scale'} && length($Dat{'lat'})) {
938 $Dat{'lat'} *= 2;
940 if ($Opt{'output-format'} eq "gpsml") {
941 if ($Dat{'what'} eq "tp") {
942 $Dat{'format'} = "gpsml";
943 $Line .= trackpoint(%Dat);
944 } elsif ($Dat{'what'} =~ /^(pause|desc|title)$/) {
945 $Line .= sprintf("<%s>%s</%s>\n",
947 $Dat{$1},
948 $1);
950 } elsif ($Opt{'output-format'} eq "pgtab") {
951 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
952 $Dat{'format'} = "pgtab";
953 $Line .= trackpoint(%Dat);
955 } elsif ($Opt{'output-format'} eq "xgraph") {
956 if ($print_pos && !length($Dat{'error'})) {
957 $Dat{'format'} = "xgraph";
958 $Line .= trackpoint(%Dat);
960 } elsif($Opt{'output-format'} eq "gpstrans") {
961 if ($print_pos && !length($Dat{'error'})) {
962 $Dat{'format'} = "gpstrans";
963 $Line .= trackpoint(%Dat);
965 } elsif($Opt{'output-format'} eq "gpx") {
966 if ($Dat{'what'} eq "tp") {
967 $Dat{'format'} = "gpx";
968 $Line .= trackpoint(%Dat);
970 } elsif ($Opt{'output-format'} eq "clean") {
971 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
972 $Dat{'format'} = "clean";
973 $Line .= trackpoint(%Dat);
975 } elsif ($Opt{'output-format'} eq "ps") {
976 $Line .= (
977 $pause_len
978 ? "f\n$Dat{'lon'} $Dat{'lat'} m\n"
979 : "$Dat{'lon'} $Dat{'lat'} l\n"
981 } elsif ($Opt{'output-format'} eq "svg") {
982 $Line .= (
983 ($last_lon == 1000) || $pause_len
984 ? join("",
985 "$svg_start_thing<path\n",
986 " stroke=\"blue\"\n",
987 " stroke-width=\"0.001\"\n",
988 " fill=\"none\"\n",
989 " d=\"\n",
990 "M $Dat{'lon'} $Dat{'lat'}\n")
991 : "L $Dat{'lon'} $Dat{'lat'}\n"
993 } elsif ($Opt{'output-format'} eq "ygraph") {
994 if (!length($Dat{'error'})) {
995 my $Time = $print_time ? ($ep_time - $first_time) * 1 : 0;
996 $Line .= "\"Time = $Time.0\n$Dat{'lon'} $Dat{'lat'}\n\n";
998 } elsif ($Opt{'output-format'} eq "csv") {
999 # {{{
1000 if (!length($Dat{'error'})) {
1001 $Dat{'format'} = "csv";
1002 $Line .= join("\t",
1003 $print_time
1004 ? $Opt{'epoch'}
1005 ? $ep_time
1006 : $Opt{'short-date'}
1007 ? "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
1008 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z"
1009 : "$Dat{'year'}-$Dat{'month'}-$Dat{'day'}T" .
1010 "$Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}Z"
1011 : "",
1012 $Dat{'lon'},
1013 $Dat{'lat'},
1014 $print_ele ? $Dat{'ele'} : "", # Elevation
1015 "\n"
1018 # }}}
1019 } elsif ($Opt{'output-format'} eq "pgwtab") {
1020 # FIXME: NOP at the moment.
1021 } else {
1022 die("$progname: \"$Opt{'output-format'}\": " .
1023 "Unknown output format\n");
1025 # }}}
1028 if (!$last_time && $Opt{'output-format'} eq "ps") {
1029 $Line .= "$Dat{'lon'} $Dat{'lat'} m\n";
1032 if ($do_print) {
1033 if ($found_move) {
1034 if ($Opt{'output-format'} eq "gpsml") {
1035 $Line = "<break/>\n$Line";
1037 (!$pause_len && ($Opt{'output-format'} eq "xgraph"))
1038 && ($Line .= "move $Line");
1039 ($Opt{'output-format'} eq "clean") && ($Line .= "\n");
1040 if ($Opt{'output-format'} eq "gpx") {
1041 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n" .
1042 "$Spc$Spc$Spc$Spc<trkseg>\n";
1044 $found_move = 0;
1046 print($Line);
1048 $print_time && ($last_time = $ep_time);
1049 if ($print_pos) {
1050 $last_lon = $Dat{'lon'};
1051 $last_lat = $Dat{'lat'};
1053 $last_line = $data_line;
1054 $svg_start_thing = "\"/>\n";
1055 # }}}
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 # }}}
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 -s, --short-date
1194 Use short date format.
1195 -S, --save-to-file x
1196 Save the unconverted data to a file with a filename starting with
1197 the timestamp of the first trackpoint. The parameter string x is
1198 added at the end of the filename. For the time being this option
1199 will ignore all other options. Note: If several files are specified
1200 on the command line, all data will be saved into only one file. This
1201 behaviour may change in the future.
1202 -t, --create-breaks
1203 Create breaks in track between points with a difference more than
1204 $PAUSE_LIMIT seconds.
1205 -T x, --time-shift x
1206 Move time of trackpoint x seconds forwards or backwards. x can be a
1207 positive or negative integer.
1208 -v, --verbose
1209 Increase level of verbosity. Can be repeated.
1210 --version
1211 Print version information.
1212 -w, --strip-whitespace
1213 Strip all unnecessary whitespace.
1214 -y, --double-y-scale
1215 Double Y scale (latitude) to get it right in gnuplot.
1216 --debug
1217 Print debugging messages.
1220 exit($Retval);
1221 # }}}
1222 } # usage()
1224 sub msg {
1225 # Print a status message to stderr based on verbosity level {{{
1226 my ($verbose_level, $Txt) = @_;
1228 if ($Opt{'verbose'} >= $verbose_level) {
1229 print(STDERR "$progname: $Txt\n");
1231 # }}}
1232 } # msg()
1234 __END__
1236 # Law talk {{{
1237 # Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1239 This program is free software: you can redistribute it and/or modify it
1240 under the terms of the GNU General Public License as published by the
1241 Free Software Foundation, either version 3 of the License, or (at your
1242 option) any later version.
1244 This program is distributed in the hope that it will be useful, but
1245 WITHOUT ANY WARRANTY; without even the implied warranty of
1246 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1247 See the GNU General Public License for more details.
1249 You should have received a copy of the GNU General Public License along
1250 with this program.
1251 If not, see L<http://www.gnu.org/licenses/>.
1252 # }}}
1254 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :