gpsfold: Remove $Id$ at EOF.
[gpstools.git] / gpst
blob5bfffa781893c97b0031a957a4b0739ecb17269f
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 'near' => "",
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 "near" => \$Opt{'near'},
81 "output-format|o=s" => \$Opt{'output-format'},
82 "outside" => \$Opt{'outside'},
83 "pos1=s" => \$Opt{'pos1'},
84 "pos2=s" => \$Opt{'pos2'},
85 "require|r=s" => \$Opt{'require'},
86 "round|R=s" => \$Opt{'round'},
87 "save-to-file|S=s" => \$Opt{'save-to-file'},
88 "short-date|s" => \$Opt{'short-date'},
89 "skip-dups|d" => \$Opt{'skip-dups'},
90 "strip-whitespace|w" => \$Opt{'strip-whitespace'},
91 "time-shift|T=i" => \$Opt{'time-shift'},
92 "undefined|n=s" => \$Opt{'undefined'},
93 "verbose|v+" => \$Opt{'verbose'},
94 "version" => \$Opt{'version'},
96 # }}}
97 ) || die("$progname: Option error. Use -h for help.\n");
99 my %Dat;
101 my $PAUSE_LIMIT = 2 * 60; # Antall sekunder mellom to punkter det må til før en move legges inn.
102 my $Udef = "?";
103 my $DIGIT = '[0-9\.\-\+]'; # Used in regexps
104 $GPST::Spc = $Opt{'strip-whitespace'} ? "" : " ";
105 my $Spc = $GPST::Spc; # FIXME
106 my $found_move = 0; # Settes til 1 hvis en /^# move$/ blir funnet.
107 my $first_time = 0;
108 my $last_time = 0;
109 my ($last_lon, $last_lat, $last_line) =
110 ( 1000, 1000, ""); # Vi kan jo teoretisk sett være i Greenwich eller på ekvator
111 my ($lat1, $lon1, $lat2, $lon2) =
112 (-1000, -1000, 1000, 1000);
114 our %Cmd = (
115 'gpsbabel' => '/usr/local/bin/gpsbabel',
118 my %Poscount = ();
120 if ($Opt{'output-format'} eq "pgtab") {
121 $Opt{'require'} .= "p";
123 my %Req = (
124 'ele' => ($Opt{'require'} =~ /e/) ? 1 : 0,
125 'position' => ($Opt{'require'} =~ /p/) ? 1 : 0,
126 'time' => ($Opt{'require'} =~ /t/) ? 1 : 0,
128 $Opt{'require'} =~ /[^ept]/
129 && die("$0: Unknown flag in --require (-r) value\n");
131 $Opt{'debug'} && ($Debug = 1);
132 $Opt{'help'} && usage(0);
133 if ($Opt{'version'}) {
134 print_version();
135 exit(0);
138 if ($Opt{'pos1'} =~ /^($DIGIT+),($DIGIT+)$/) {
139 $lat1 = $1;
140 $lon1 = $2;
142 if ($Opt{'pos2'} =~ /^($DIGIT+),($DIGIT+)$/) {
143 $lat2 = $1;
144 $lon2 = $2;
146 if ($lat1 > $lat2) {
147 my $Tmp = $lat1;
148 $lat1 = $lat2;
149 $lat2 = $Tmp;
151 if ($lon1 > $lon2) {
152 my $Tmp = $lon1;
153 $lon1 = $lon2;
154 $lon2 = $Tmp;
157 if ($Opt{'epoch'} && $Opt{'short-date'}) {
158 die("$progname: Cannot mix the --epoch (-e) and --short-date (-s) options\n");
161 if ($Opt{'inside'} && $Opt{'outside'}) {
162 die("$progname: Cannot mix the --inside and --outside options\n");
165 # To avoid printing out extra "/> at the start of svg output:
166 my $svg_start_thing = "";
168 my %Round = ();
170 if (defined($Opt{'round'})) {
171 my $R = $Opt{'round'};
172 $R =~ s/([a-z]+)=(\d+)/($Round{$1}=$2, "")/eg;
175 length($Opt{'undefined'}) && ($Udef = $Opt{'undefined'});
177 $Opt{'save-to-file'} eq "\n" && print_header(*STDOUT);
179 my @first_lines;
180 my $xml_data;
181 my $data_line = "";
182 our $curr_file = "";
184 my $from_stdin = scalar(@ARGV) ? 0 : 1;
186 $from_stdin && push(@ARGV, "-");
188 for $curr_file (@ARGV) {
189 # Scan through stdin or specified files and send every GPS entry to
190 # print_entry()
191 # {{{
192 print(STDERR "$progname: Opening \"$curr_file\" for read\n") if $Opt{'verbose'};
193 if (open(CurrFP, "<$curr_file")) {
194 # {{{
195 while (<CurrFP>) {
196 $data_line = $_;
197 %Dat = (
198 'year' => '', 'month' => '', 'day' => '',
199 'hour' => '', 'min' => '', 'sec' => '',
200 'epoch' => '',
201 'date-format' => '',
202 'lat' => '', 'lon' => '',
203 'ele' => '',
204 'desc' => '',
205 'error' => "",
206 'what' => 'tp',
209 $Opt{'epoch'} && ($Dat{'date-format'} = "epoch");
210 $Opt{'short-date'} && ($Dat{'date-format'} = "short");
212 if ($Opt{'save-to-file'} ne "\n") {
213 push(@first_lines, $_);
215 s/^# error // && ($Dat{'error'} = "error");
216 s/^# ?// && ($Dat{'error'} = "desc");
217 $xml_data = "";
218 if (m#^<(e?tp)\b(.*?)>(.*?)</(e?tp)>\s*$#) {
219 # gpsml — The main storage format {{{
220 my ($Elem, $Props, $Data) =
221 ( $1, $2, $3);
222 my $err_str = ($Props =~ /\berr="(.*?)"/) ? $1 : "error";
223 $Elem eq "etp" && ($Dat{'error'} = $err_str);
224 my $Time = "";
225 $Data =~ m#<time>(.*?)</time># && ($Time = $1);
226 $Time =~ s{
227 (\d\d\d\d)-?(\d\d)-?(\d\d)[T ](\d\d):?(\d\d):?([\d\.]+?)Z
229 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
230 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
231 ( $1, $2, $3,
232 $4, $5, $6);
234 }ex;
235 $Data =~ m#<lat>($DIGIT*?)</lat># && ($Dat{'lat'} = $1);
236 $Data =~ m#<lon>($DIGIT*?)</lon># && ($Dat{'lon'} = $1);
237 $Data =~ m#<ele>($DIGIT*?)</ele># && ($Dat{'ele'} = $1);
238 $Data =~ m#<desc>(.*?)</desc># && ($Dat{'desc'} = $1);
239 print_entry(%Dat);
240 # }}}
241 } elsif (m#^<break\b.*?/>#) {
242 $found_move = 1;
243 } elsif (m#^<(title|pause)\b.*?>(.*?)</(title|pause)>#) {
244 $Dat{'what'} = $1;
245 $Dat{$1} = $2;
246 print_entry(%Dat);
247 } elsif (m#^<desc\b.*?>(.*$)#s) {
248 $Dat{'what'} = "desc";
249 my $Txt = $1;
250 until ($Txt =~ m#</desc>#s) {
251 $Txt .= <CurrFP>;
253 $Txt =~ s#^(.*)(</desc>.*$)#$1#s;
254 $Dat{'desc'} = $Txt;
255 print_entry(%Dat);
256 } elsif (/<gpx\b/) {
257 $xml_data = $_;
258 $xml_data .= join("", <CurrFP>);
259 if (!length($Opt{'output-format'})) {
260 $Opt{'output-format'} = "gpx";
261 print_header(*STDOUT);
263 read_xmlfile($xml_data);
264 last;
265 } elsif (/^move$/) {
266 $found_move = 1;
267 } elsif (m#^(\d+)\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
268 # CSV format, epoch style {{{
269 my ($ep_time, $lon_val, $lat_val, $Alt) =
270 ( $1, $2, $3, $4);
271 $Dat{'epoch'} = $ep_time;
272 ($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
273 $Dat{'day'}, $Dat{'month'}, $Dat{'year'},
274 $Dat{'wday'}, $Dat{'yday'}) = gmtime($ep_time);
275 $Dat{'month'}++; # Urgh Ⅰ
276 $Dat{'year'} += 1900; # Urgh Ⅱ
277 print_entry(%Dat);
278 # }}}
279 } elsif (
281 (\d\d\d\d)-?(\d\d)-?(\d\d)[T\ ](\d\d):?(\d\d):?(\d\d)Z?\t
282 ($DIGIT+)\t($DIGIT+)\t($DIGIT)
285 # CSV format, human-readable date format {{{
286 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
287 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
288 $Dat{'lon'}, $Dat{'lat'}, $Dat{'ele'}) =
289 ($1, $2, $3,
290 $4, $5, $6,
291 $7, $8, $9);
292 print_entry(%Dat);
293 # }}}
294 } elsif (/^Trackpoint\t/) {
295 # 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 {{{
297 # Trackpoint\t
298 # N60.41630 E5.31675\t
299 # 09.02.2006 20:24:37 (UTC)\t
300 # 13.6 m\t
301 # \t
302 # 93.9 m\t
303 # 00:00:06\t
304 # 56 kph\t
305 # 123° true
306 my $Orig = $_;
307 $Orig =~ s/[\r\n]+$//;
308 my ($Marker_f, $Position_f, $Time_f, $Alt_f, $Depth_f,
309 $Leglength_f, $Legtime_f, $Legspeed_f, $Legcourse_f) =
310 split(/\t/, $Orig .
311 # Nødløsning for å unngå at variabler
312 # blir udefinert.
313 "\t\t\t\t\t\t\t\t\t\t"
315 # D(join("",
316 # "Position_f=\"$Position_f\" \x7B\x7B\x7B\n",
317 # "Time_f=\"$Time_f\"\n",
318 # "Alt_f=\"$Alt_f\"\n",
319 # "Depth_f=\"$Depth_f\"\n",
320 # "Leglength_f=\"$Leglength_f\"\n",
321 # "Legtime_f=\"$Legtime_f\"\n",
322 # "Legspeed_f=\"$Legspeed_f\"\n",
323 # "Legcourse_f=\"$Legcourse_f\" \x7D\x7D\x7D\n",
324 # ));
325 my ($NS, $WE,
326 $Alt_unit,
327 $Leglength,
328 $Legtime_hour, $Legtime_min, $Legtime_sec,
329 $Legspeed, $Legspeed_unit,
330 $Legcourse
331 ) = ("", "", "", "", "", "", "", "", "", "", "", "", "",
332 "", "", "", "", "", "", "", "", "", "");
333 ($Position_f =~ /^(N|S)([\d\.]+) (W|E)([\d\.]+)/) &&
334 ($NS = $1, $Dat{'lat'} = $2, $WE = $3, $Dat{'lon'} = $4);
335 ($Time_f =~ /^(\d+)\.(\d+)\.(\d+) (\d+):(\d+):(\d+) \((.+?)\)/) &&
336 ($Dat{'day'} = $1, $Dat{'month'} = $2, $Dat{'year'} = $3,
337 $Dat{'hour'} = $4, $Dat{'min'} = $5, $Dat{'sec'} = $6);
338 ($Alt_f =~ /^($DIGIT+) (.*?)/) &&
339 ($Dat{'ele'} = $1, $Alt_unit = $2);
340 # D("ele = \"$Dat{'ele'}\"");
341 ($NS eq "S") && ($Dat{'lat'} = 0-$Dat{'lat'});
342 ($WE eq "W") && ($Dat{'lon'} = 0-$Dat{'lon'});
343 # MapSource in win xp writes YYYY, but YY in win98se.
345 defined($Dat{'year'})
346 && $Dat{'year'} =~ /\d/
347 && $Dat{'year'} < 1900
348 ) && ($Dat{'year'} += 2000);
349 print_entry(%Dat);
350 # }}}
351 } elsif (/^Track\t(.*?)\t/) {
352 $Dat{'title'} = txt_to_xml($1);
353 $Dat{'what'} = "title";
354 $found_move = 1;
355 print_entry(%Dat);
356 } elsif (
359 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)\t
360 (.+)\xB0(.+)'(.+)"\t
361 (.+)\xB0(.+)'(.+)"
364 # T 09/01/2002 11:51:26 60°23'36.3" 5°19'35.9" {{{
365 my ($lat_d, $lat_m, $lat_s, $lon_d, $lon_m, $lon_s);
366 ($Dat{'month'}, $Dat{'day'}, $Dat{'year'},
367 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
368 $lat_d, $lat_m, $lat_s,
369 $lon_d, $lon_m, $lon_s) =
370 ($1, $2, $3,
371 $4, $5, $6,
372 $7, $8, $9,
373 $10, $11, $12);
374 my $Flat = defined($Round{'lat'}) ? ".$Round{'lat'}" : "";
375 my $Flon = defined($Round{'lon'}) ? ".$Round{'lon'}" : "";
376 $Dat{'lat'} = sprintf("%${Flat}f",
377 1.0*($lat_d+($lat_m/60)+($lat_s/3600)));
378 $Dat{'lon'} = sprintf("%${Flon}f",
379 1.0*$lon_d+($lon_m/60)+($lon_s/3600));
380 print_entry(%Dat);
381 # }}}
382 } elsif (
384 1\ (\S+)\ (\S+)\ (\S+)\ (\S+)\x20
385 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)
388 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
389 ($Dat{'lat'}, $Dat{'lon'}, $Dat{'speed'},
390 $Dat{'unkn'},
391 $Dat{'month'}, $Dat{'day'}, $Dat{'year'},
392 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
393 ($1, $2, $3,
395 $5, $6, $7,
396 $8, $9, $10);
397 print_entry(%Dat);
398 # }}}
399 } elsif (/^
400 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
401 # Regexp {{{
402 (@) # @
403 (\d\d) # Year
404 (\d\d) # Month
405 (\d\d) # Day
406 (\d\d) # Hours
407 (\d\d) # Minutes
408 (\d\d) # Seconds
409 ([NS]) # N|S
410 (\d\d) # Latitude degree
411 (\d\d) # Latitude minute
412 (\d\d\d) # Latitude minute decimals
413 ([EW]) # E|W
414 (\d\d\d) # Longitude degree
415 (\d\d) # Longitude minute
416 (\d\d\d) # Longitude minute degree
417 (....) # Accurancy
418 (......) # Elevation
419 (...............)
420 # }}}
421 /x) {
422 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
423 $lon_degmin, $lon_mindec);
424 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'}, $Dat{'hour'},
425 $Dat{'min'}, $Dat{'sec'}, $NS, $lat_deg,
426 $lat_degmin, $lat_mindec, $EW,
427 $lon_deg, $lon_degmin, $lon_mindec,
428 $Dat{'accur'}, $Dat{'ele'}, $Dat{'unknown'}) =
429 ($2+2000, $3, $4, $5,
430 $6, $7, $8, $9,
431 $10, $11, $12,
432 $13, $14, $15,
433 $16, $17, $18);
434 my $ep_time = timegm_nocheck(
435 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
436 $Dat{'day'}, $Dat{'month'}-1, $Dat{'year'}
438 $last_time = $ep_time;
439 my $Flat = defined($Round{'lat'}) ? ".$Round{'lat'}" : "";
440 my $Flon = defined($Round{'lon'}) ? ".$Round{'lon'}" : "";
441 my $tmp_lon = sprintf(
442 "%${Flon}f",
443 $lon_deg +
444 $lon_degmin/60 +
445 $lon_mindec/60000);
446 my $tmp_lat = sprintf("%${Flat}f",
447 $lat_deg +
448 $lat_degmin/60 +
449 $lat_mindec/60000);
450 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
451 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
452 $Dat{'lat'} = $tmp_lat;
453 $Dat{'lon'} = $tmp_lon;
454 print_entry(%Dat);
455 # }}}
456 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(_{42})/) {
457 # @020721221336__________________________________________ {{{
458 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
459 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $Dat{'rest'}) =
460 ($2+2000, $3, $4,
461 $5, $6, $7, $8);
462 $Dat{'error'} = "nosignal";
463 print_entry(%Dat);
464 # }}}
465 } elsif (/^xmaplog /) {
466 # NOP
467 } elsif (/^$/) {
468 ($Opt{'output-format'} eq "csv")
469 && ($Opt{'save-to-file'} eq "\n")
470 && print("\n");
471 } elsif (/^Pause: /) {
472 # NOP, is here to cope with old files I’ve lying around.
473 } elsif ($Dat{'error'} eq "desc") {
474 my $Comment = $_;
475 if (defined($Comment)) {
476 $Comment =~ s/^\s*(.*?)\s*$/$1/;
477 if ($Opt{'output-format'} eq "gpsml") {
478 $Dat{'desc'} = txt_to_xml($Comment);
479 $Dat{'what'} = "desc";
480 print_entry(%Dat);
483 } else {
484 $Opt{'verbose'} && warn("Line $.: Unknown: \"$_\"\n");
487 # }}}
488 } else {
489 warn("$progname: $curr_file: Cannot open file for read: $!\n");
491 # }}}
494 print_footer(*STDOUT);
496 exit(0);
498 sub read_xmlfile {
499 # {{{
500 my $Txt = join("", @_);
501 $Txt =~ s/<!--(.*?)-->//gs;
502 $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
503 # }}}
506 sub print_gpx {
507 # {{{
508 my $Orig = shift;
509 my $Str = $Orig;
510 # D("print_xml_gps(\"$Orig\")\n");
511 $Str =~ s/<!--(.*?)-->//gs;
512 my $fromdate_str = "";
513 if ($Opt{'from-date'}) {
514 $fromdate_str = "date >= '$Opt{'from-date'}' AND ";
516 if ($Opt{'output-format'} =~ /^(pgwtab|pgwupd)$/) {
517 # {{{
518 $Str =~
520 <wpt\b(.*?)>(.*?)</wpt>
523 my $attr_wpt = $1;
524 my $el_wpt = $2;
525 my ($Lat, $Lon, $Name, $Ele, $Type, $Time, $Cmt, $Desc, $Src, $Sym) =
526 ('\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N');
528 $attr_wpt =~ /.*lat="($DIGIT+?)"/s &&
529 ($Lat = postgresql_copy_safe($1));
530 $attr_wpt =~ /.*lon="($DIGIT+?)"/s &&
531 ($Lon = postgresql_copy_safe($1));
532 $el_wpt =~ /.*<name\b(.*?)>(.*?)<\/name>/s &&
533 ($Name = postgresql_copy_safe(xml_to_txt($2)));
534 $el_wpt =~ /.*<ele\b(.*?)>(.*?)<\/ele>/s &&
535 ($Ele = postgresql_copy_safe(xml_to_txt($2)));
536 $el_wpt =~ /.*<type\b(.*?)>(.*?)<\/type>/s &&
537 ($Type = postgresql_copy_safe(xml_to_txt($2)));
538 $el_wpt =~ /.*<time\b(.*?)>(.*?)<\/time>/s &&
539 ($Time = postgresql_copy_safe(xml_to_txt($2)));
540 $el_wpt =~ /.*<cmt\b(.*?)>(.*?)<\/cmt>/s &&
541 ($Cmt = postgresql_copy_safe(xml_to_txt($2)));
542 $el_wpt =~ /.*<desc\b(.*?)>(.*?)<\/desc>/s &&
543 ($Desc = postgresql_copy_safe(xml_to_txt($2)));
544 $el_wpt =~ /.*<src\b(.*?)>(.*?)<\/src>/s &&
545 ($Src = postgresql_copy_safe(xml_to_txt($2)));
546 $el_wpt =~ /.*<sym\b(.*?)>(.*?)<\/sym>/s &&
547 ($Sym = postgresql_copy_safe(xml_to_txt($2)));
549 if (length($Opt{'round'})) {
550 if (defined($Round{'lat'}) && length($Lat)) {
551 ($Lat = 1.0 * sprintf("%.$Round{'lat'}f", $Lat));
553 if (defined($Round{'lon'}) && length($Lon)) {
554 ($Lon = 1.0 * sprintf("%.$Round{'lon'}f", $Lon));
556 if (defined($Round{'ele'}) && $Ele ne '\N') {
557 ($Ele = 1.0 * sprintf("%.$Round{'ele'}f", $Ele));
561 if ($Opt{'output-format'} eq "pgwtab") {
562 print(
563 join("\t",
564 "($Lat,$Lon)",
565 $Name,
566 $Ele,
567 $Type,
568 $Time,
569 $Cmt,
570 $Desc,
571 $Src,
572 $Sym
573 ) . "\n"
575 } elsif ($Opt{'output-format'} eq "pgwupd") {
576 $Name =~ s/'/''/gs;
577 print(join("\n",
578 "BEGIN;",
579 "$Spc${Spc}UPDATE logg SET name = clname(coor) " .
580 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
581 "$Spc${Spc}UPDATE logg SET dist = cldist(coor) " .
582 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
583 "COMMIT;"
584 ) . "\n");
587 }gsex;
588 # }}}
589 } else {
590 # {{{
591 $Str =~
593 <trk\b(.*?)>(.*?)</trk>
596 my $el_trk = $2;
597 $el_trk =~
599 <name\b(.*?)>(.*?)</name>
601 my %tmp_dat = ();
602 $tmp_dat{'title'} = $2;
603 $tmp_dat{'what'} = "title";
604 $tmp_dat{'error'} = "";
605 print_entry(%tmp_dat);
607 }sex;
608 $el_trk =~
610 <trkseg\b(.*?)>(.*?)</trkseg>
613 my $el_trkseg = $2;
614 $el_trkseg =~
616 <trkpt\b(.*?)>(.*?)</trkpt>
619 my ($attr_trkpt, $el_trkpt) =
620 ( $1, $2);
621 %Dat = (
622 'year' => '', 'month' => '', 'day' => '',
623 'hour' => '', 'min' => '', 'sec' => '',
624 'epoch' => '',
625 'date-format' => '',
626 'lat' => '', 'lon' => '',
627 'ele' => '',
628 'desc' => '',
629 'error' => "",
630 'what' => 'tp',
632 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon'} = $1);
633 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat'} = $1);
634 ($el_trkpt =~ m#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele'} = $1);
635 if (
636 $el_trkpt =~
638 <time>(\d\d\d\d)-?(\d\d)-?(\d\d)T
639 (\d\d):?(\d\d):?([\d\.]+)Z</time>
642 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
643 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
644 ($1, $2, $3, $4, $5, $6);
646 print_entry(%Dat);
648 }gsex;
649 $found_move = 1;
650 }gsex;
651 $found_move = 1;
652 }gsex;
653 # }}}
655 # }}}
658 sub print_header {
659 # {{{
660 local *OutFP = shift;
661 if ($Opt{'output-format'} eq "gpsml") {
662 print(OutFP join("",
663 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
664 "<gpsml>\n",
665 "<track>\n",
667 } elsif ($Opt{'output-format'} eq "gpstrans") {
668 print(OutFP "Format: DMS UTC Offset: 0.00 hrs " .
669 "Datum[100]: WGS 84\n");
670 } elsif ($Opt{'output-format'} eq "gpx") {
671 print(OutFP join("",
672 qq{<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n},
673 qq{<gpx\n},
674 qq{$Spc${Spc}version="1.1"\n},
675 qq{$Spc${Spc}creator="gpst - http://svn.sunbase.org/repos/utils/trunk/src/gpstools/"\n},
676 qq{$Spc${Spc}xmlns="http://www.topografix.com/GPX/1/1"\n},
677 qq{$Spc${Spc}xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n},
678 qq{$Spc${Spc}xsi:schemaLocation="http://www.topografix.com/GPX/1/1 },
679 qq{http://www.topografix.com/GPX/1/1/gpx.xsd"\n},
680 qq{>\n},
681 qq{$Spc$Spc<trk>\n},
682 qq{$Spc$Spc$Spc$Spc<trkseg>\n},
684 } elsif ($Opt{'output-format'} eq "ps") {
685 print(OutFP ps_header(532, 6034, 533, 6040));
686 print(OutFP "*u\n");
687 } elsif ($Opt{'output-format'} eq "svg") {
688 print(OutFP join("",
689 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
690 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
691 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
692 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
693 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
694 "$Spc$Spc<title></title>\n",
695 "$Spc$Spc<desc></desc>\n",
698 # }}}
701 sub print_footer {
702 # Print footer {{{
703 local *OutFP = shift;
704 if ($Opt{'output-format'} eq "gpsml") {
705 print(OutFP join("",
706 "</track>\n",
707 "</gpsml>\n",
709 } elsif ($Opt{'output-format'} eq "gpx") {
710 print(OutFP join("",
711 "$Spc$Spc$Spc$Spc</trkseg>\n",
712 "$Spc$Spc</trk>\n",
713 "</gpx>\n",
715 } elsif ($Opt{'output-format'} eq "poscount") {
716 while (my ($l_name, $l_val) = each %Poscount) {
717 $l_name =~ /^(.+?),(.+?)$/
718 && print(OutFP "$1\t$2\t$l_val\n");
720 } elsif ($Opt{'output-format'} eq "ps") {
721 print(OutFP join("",
722 "*U\n",
723 "%%Trailer\n",
724 "%%EOF\n",
726 } elsif ($Opt{'output-format'} eq "svg") {
727 print(OutFP "\"/>\n</svg>\n");
729 # }}}
732 sub print_entry {
733 # Print a GPS entry with time, latitude, longitude and elevation in
734 # various formats
735 # {{{
736 my %Dat = @_;
737 defined($Dat{'desc'}) || ($Dat{'desc'} = "");
738 defined($Dat{'ele'}) || ($Dat{'ele'} = "");
739 defined($Dat{'lat'}) || ($Dat{'lat'} = "");
740 defined($Dat{'lon'}) || ($Dat{'lon'} = "");
741 defined($Dat{'year'}) || ($Dat{'year'} = "");
742 my $print_time = length($Dat{'year'}) ? 1 : 0;
743 my $print_pos;
744 if (!$Req{'position'} && $Opt{'output-format'} eq "gpsml") {
745 $print_pos = (length($Dat{'lat'}) || length($Dat{'lon'})) ? 1 : 0;
746 } else {
747 $print_pos = (length($Dat{'lat'}) && length($Dat{'lon'})) ? 1 : 0;
749 if (!$print_pos) {
750 $Dat{'lat'} = $Dat{'lon'} = "";
752 my $print_ele = length($Dat{'ele'}) ? 1 : 0;
753 my $print_desc = length($Dat{'desc'}) ? 1 : 0;
754 my $Line = "";
755 # D("print_entry(\"" . join("\", \"", @_) . "\");");
756 my $ep_time;
758 if ($Opt{'near'} && $print_pos) {
759 $Line .= sprintf("%s ",
760 list_nearest_waypoints($Dat{'lat'}, $Dat{'lon'}));
763 if (length($Opt{'round'})) {
764 for my $Tmp (qw{ lat lon ele }) {
765 if (defined($Round{$Tmp}) && length($Dat{$Tmp})) {
766 # D("Tmp = '$Tmp'");
767 ($Dat{$Tmp} = 1.0 * sprintf("%.$Round{$Tmp}f", $Dat{$Tmp}));
772 if ($Opt{'output-format'} eq "poscount") {
773 if (!length($Dat{'error'})) {
774 my $Name = "$Dat{'lon'},$Dat{'lat'}";
775 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
776 $Poscount{$Name}++;
778 return;
781 if ($print_time) {
782 $ep_time = timegm_nocheck(
783 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
784 $Dat{'day'}, $Dat{'month'} - 1, $Dat{'year'}
786 if ($Opt{'time-shift'}) {
787 # D("ep_time før: '$ep_time'");
788 $ep_time += $Opt{'time-shift'};
789 # D("ep_time etter: '$ep_time'");
790 ($Dat{'sec'}, $Dat{'min'},$Dat{'hour'}, $Dat{'day'},
791 $Dat{'month'}, $Dat{'year'}) = gmtime($ep_time);
792 $Dat{'year'} += 1900;
793 $Dat{'month'}++;
795 $Dat{'epoch'} = $ep_time;
796 $Dat{'year'} = sprintf("%04u", $Dat{'year'});
797 $Dat{'month'} = sprintf("%02u", $Dat{'month'});
798 $Dat{'day'} = sprintf("%02u", $Dat{'day'});
799 $Dat{'hour'} = sprintf("%02u", $Dat{'hour'});
800 $Dat{'min'} = sprintf("%02u", $Dat{'min'});
801 $Dat{'sec'} = sprintf("%02u", $Dat{'sec'});
802 if ($Opt{'chronology'}) {
803 if ($last_time > $ep_time && !length($Dat{'error'})) {
804 warn(sprintf(
805 "%s: $curr_file: \"%sZ\": Next date is %s in the past (%sZ)\n",
806 $progname, sec_to_string($last_time, "T"),
807 sec_to_readable($last_time-$ep_time),
808 sec_to_string($ep_time, "T")
810 # FIXME: Make --fix work with gpx.
811 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
812 $Dat{'error'} = "chrono";
814 } elsif ($last_time == $ep_time && !length($Dat{'error'})) {
815 warn(sprintf(
816 "%s: $curr_file: \"%sZ\": Duplicated time\n",
817 $progname, sec_to_string($last_time, "T")
819 # FIXME: Make --fix work with gpx.
820 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
821 $Dat{'error'} = "duptime";
825 } else {
826 $ep_time = 0;
827 $Dat{'year'} = 0;
828 $Dat{'month'} = 0;
829 $Dat{'day'} = 0;
830 $Dat{'hour'} = 0;
831 $Dat{'min'} = 0;
832 $Dat{'sec'} = 0;
835 if ($Opt{'save-to-file'} ne "\n") {
836 # {{{
837 $print_time || return;
838 my $base_name = "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
839 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z" .
840 "$Opt{'save-to-file'}";
841 my $file_name = $base_name;
842 if (-e $file_name) {
843 for (my $a = 1; (-e $file_name) && ($a < 1000); $a++) {
844 $file_name = "$base_name.dup_$a";
846 if (-e $file_name) {
847 die("$progname: $base_name: File already exists, and ran " .
848 "out of attempts to create unique file name\n");
850 if ($Opt{'verbose'}) {
851 warn("$progname: $base_name: File already exists, using " .
852 "unique name \"$file_name\" instead\n");
855 if (open(ToFP, ">", $file_name)) {
856 print_header(*ToFP);
857 print(ToFP (
858 $from_stdin
859 ? @first_lines
860 : ()),
861 (length($xml_data)
862 ? $xml_data
863 : <>)
864 ) || die("$progname: $file_name: Cannot write to file: $!\n");
865 print_footer(*ToFP);
866 close(ToFP);
867 if ($Opt{'output-format'} eq "gpsml") {
868 printf("<include>%s</include>\n",
869 txt_to_xml($file_name));
870 } elsif ($Opt{'output-format'} eq "gpx") {
871 printf("<!-- Saved unconverted data to \"%s\" -->\n",
872 txt_to_xml($file_name));
873 } else {
874 print("$progname: Saved unconverted data to \"$file_name\"\n");
876 exit 0;
877 } else {
878 die("$progname: $file_name: Cannot create file: $!\n");
880 # }}}
883 my $pause_len = 0;
884 my $do_print = 1;
886 if ($Dat{'what'} eq "tp") {
887 # {{{
888 if ($Opt{'require'}) {
889 $Req{'time'} && !$print_time && return;
890 $Req{'position'} && !$print_pos && return;
891 $Req{'ele'} && !$print_ele && return;
894 if ($Opt{'inside'} || $Opt{'outside'}) {
895 if (
896 ($Dat{'lat'} < $lat1) ||
897 ($Dat{'lat'} > $lat2) ||
898 ($Dat{'lon'} < $lon1) ||
899 ($Dat{'lon'} > $lon2)
901 $Opt{'inside'} && return;
902 } else {
903 $Opt{'outside'} && return;
907 if ($Opt{'output-format'} eq "ps") {
908 $Dat{'lon'} *= 100;
909 $Dat{'lat'} *= 100;
912 if (
913 $Opt{'skip-dups'}
914 && ($Dat{'lon'} eq $last_lon)
915 && ($Dat{'lat'} eq $last_lat)
917 if ($Opt{'output-format'} eq 'gpsml') {
918 $Dat{'error'} = "dup";
919 } else {
920 $do_print = 0;
922 } else {
923 $do_print = 1;
926 if (
927 $Opt{'create-breaks'}
928 && $ep_time-$last_time > $PAUSE_LIMIT
929 && $last_time
931 $pause_len = $ep_time-$last_time;
932 # D("pause_len set to '$pause_len'");
935 if ($pause_len) {
936 if ($Opt{'output-format'} eq "gpsml") {
937 $Line .= sprintf("<pause>%s</pause>\n",
938 sec_to_readable($ep_time-$last_time));
939 } elsif ($Opt{'output-format'} eq "clean") {
940 $pause_len && ($Line .= "\n");
941 } elsif ($Opt{'output-format'} eq "csv") {
942 $Line .= sprintf("# Pause: %s\n# move\n",
943 sec_to_readable($ep_time-$last_time));
944 } elsif ($Opt{'output-format'} eq "xgraph") {
945 $pause_len && ($Line .= "move ");
948 # }}}
951 if ($do_print) {
952 # Valid data was found, send to stdout {{{
953 unless ($first_time) {
954 $first_time = $ep_time;
956 if ($Opt{'double-y-scale'} && length($Dat{'lat'})) {
957 $Dat{'lat'} *= 2;
959 if ($Opt{'output-format'} eq "gpsml") {
960 if ($Dat{'what'} eq "tp") {
961 $Dat{'format'} = "gpsml";
962 $Line .= trackpoint(%Dat);
963 } elsif ($Dat{'what'} =~ /^(pause|desc|title)$/) {
964 $Line .= sprintf("<%s>%s</%s>\n",
966 $Dat{$1},
967 $1);
969 } elsif ($Opt{'output-format'} eq "pgtab") {
970 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
971 $Dat{'format'} = "pgtab";
972 $Line .= trackpoint(%Dat);
974 } elsif ($Opt{'output-format'} eq "xgraph") {
975 if ($print_pos && !length($Dat{'error'})) {
976 $Dat{'format'} = "xgraph";
977 $Line .= trackpoint(%Dat);
979 } elsif($Opt{'output-format'} eq "gpstrans") {
980 if ($print_pos && !length($Dat{'error'})) {
981 $Dat{'format'} = "gpstrans";
982 $Line .= trackpoint(%Dat);
984 } elsif($Opt{'output-format'} eq "gpx") {
985 if ($Dat{'what'} eq "tp") {
986 $Dat{'format'} = "gpx";
987 $Line .= trackpoint(%Dat);
989 } elsif ($Opt{'output-format'} eq "clean") {
990 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
991 $Dat{'format'} = "clean";
992 $Line .= trackpoint(%Dat);
994 } elsif ($Opt{'output-format'} eq "ps") {
995 $Line .= (
996 $pause_len
997 ? "f\n$Dat{'lon'} $Dat{'lat'} m\n"
998 : "$Dat{'lon'} $Dat{'lat'} l\n"
1000 } elsif ($Opt{'output-format'} eq "svg") {
1001 $Line .= (
1002 ($last_lon == 1000) || $pause_len
1003 ? join("",
1004 "$svg_start_thing<path\n",
1005 " stroke=\"blue\"\n",
1006 " stroke-width=\"0.001\"\n",
1007 " fill=\"none\"\n",
1008 " d=\"\n",
1009 "M $Dat{'lon'} $Dat{'lat'}\n")
1010 : "L $Dat{'lon'} $Dat{'lat'}\n"
1012 } elsif ($Opt{'output-format'} eq "ygraph") {
1013 if (!length($Dat{'error'})) {
1014 my $Time = $print_time ? ($ep_time - $first_time) * 1 : 0;
1015 $Line .= "\"Time = $Time.0\n$Dat{'lon'} $Dat{'lat'}\n\n";
1017 } elsif ($Opt{'output-format'} eq "csv") {
1018 # {{{
1019 if (!length($Dat{'error'})) {
1020 $Dat{'format'} = "csv";
1021 $Line .= join("\t",
1022 $print_time
1023 ? $Opt{'epoch'}
1024 ? $ep_time
1025 : $Opt{'short-date'}
1026 ? "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
1027 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z"
1028 : "$Dat{'year'}-$Dat{'month'}-$Dat{'day'}T" .
1029 "$Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}Z"
1030 : "",
1031 $Dat{'lon'},
1032 $Dat{'lat'},
1033 $print_ele ? $Dat{'ele'} : "", # Elevation
1034 "\n"
1037 # }}}
1038 } elsif ($Opt{'output-format'} eq "pgwtab") {
1039 # FIXME: NOP at the moment.
1040 } else {
1041 die("$progname: \"$Opt{'output-format'}\": " .
1042 "Unknown output format\n");
1044 # }}}
1047 if (!$last_time && $Opt{'output-format'} eq "ps") {
1048 $Line .= "$Dat{'lon'} $Dat{'lat'} m\n";
1051 if ($do_print) {
1052 if ($found_move) {
1053 if ($Opt{'output-format'} eq "gpsml") {
1054 $Line = "<break/>\n$Line";
1056 (!$pause_len && ($Opt{'output-format'} eq "xgraph"))
1057 && ($Line .= "move $Line");
1058 ($Opt{'output-format'} eq "clean") && ($Line .= "\n");
1059 if ($Opt{'output-format'} eq "gpx") {
1060 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n" .
1061 "$Spc$Spc$Spc$Spc<trkseg>\n";
1063 $found_move = 0;
1065 print($Line);
1067 $print_time && ($last_time = $ep_time);
1068 if ($print_pos) {
1069 $last_lon = $Dat{'lon'};
1070 $last_lat = $Dat{'lat'};
1072 $last_line = $data_line;
1073 $svg_start_thing = "\"/>\n";
1074 # }}}
1077 sub ps_header {
1078 # Send a Postscript header to stdout {{{
1079 my ($bl_lon, $bl_lat, $br_lon, $br_lat) = @_;
1080 my $Date = sec_to_string(time);
1081 return(join("",
1082 "%!PS-Adobe-3.0 EPSF-3.0\n",
1083 "%%Creator: gpst\n",
1084 "%%Title:\n",
1085 "%%CreationDate: $Date\n",
1086 "%%BoundingBox: $bl_lon $bl_lat $br_lon $br_lat\n",
1087 "%%DocumentData: Clean7Bit\n",
1088 "%%EndComments\n",
1089 "%%BeginProlog\n",
1090 "/bd { bind def } bind def\n",
1091 "/incompound false def\n",
1092 "/m { moveto } bd\n",
1093 "/l { lineto } bd\n",
1094 "/c { curveto } bd\n",
1095 "/F { incompound not {fill} if } bd\n",
1096 "/f { closepath F } bd\n",
1097 "/S { stroke } bd\n",
1098 "/*u { /incompound true def } bd\n",
1099 "/*U { /incompound false def f} bd\n",
1100 "/k { setcmykcolor } bd\n",
1101 "/K { k } bd\n",
1102 "%%EndProlog\n",
1103 "%%BeginSetup\n",
1104 "%%EndSetup\n",
1106 # }}}
1109 sub print_version {
1110 # Print program version {{{
1111 print("$progname v$VERSION\n");
1112 # }}}
1113 } # print_version()
1115 sub usage {
1116 # Send the help message to stdout {{{
1117 my $Retval = shift;
1119 if ($Opt{'verbose'}) {
1120 print("\n");
1121 print_version();
1123 print(<<END);
1125 Converts between various GPS formats.
1127 Usage: $progname [options] [file [files [...]]]
1128 $progname -S [file [files [...]]]
1129 $progname -u [file [files [...]]]
1131 Options:
1133 --chronology
1134 Check for broken chronology, warn about entries with an old
1135 timestamp.
1136 -d, --skip-dups
1137 Skip duplicated coordinates.
1138 -e, --epoch
1139 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1140 --fix
1141 Comment out entries which is obviously wrong. Use together with
1142 --chronology to fix those kind of errors. Does not work with GPX
1143 output yet.
1144 --from-date x
1145 Used by the pgwupd format. Specifies from which date waypoints
1146 should be updated. No checks for valid date format here, let
1147 PostgreSQL take care of that. All variants it understands can be
1148 used here.
1149 -h, --help
1150 Show this help.
1151 --inside
1152 Print only trackpoints inside a rectangle specified by --pos1 and
1153 --pos2.
1154 -n, --undefined x
1155 Use x as undefined value. Default: "$Udef".
1156 --near
1157 Add names of the three closest waypoints to the trackpoint.
1158 Unfinished and experimental, needs gpsbabel, which is called from
1159 the program as "$Cmd{'gpsbabel'}".
1160 -o, --output-format x
1161 Use output format x:
1162 clean
1164 gpsml (Default)
1165 gpstrans
1166 gpx (Not complete)
1167 pgtab
1168 pgwtab
1169 pgwupd
1170 poscount
1171 ps (Unfinished)
1172 svg (Unfinished)
1173 xgraph
1174 ygraph
1175 --outside
1176 Print only trackpoints outside a rectangle specified by --pos1 and
1177 --pos2.
1178 --pos1 x
1179 --pos2 x
1180 Specifies one corner where x is in "lat,lon" format (decimal
1181 degrees, negative for west or south) of area rectangle used by the
1182 --inside and --outside options.
1183 -r, --require x
1184 Specify requirements for trackpoints to be written. x is a string
1185 with the following flags:
1187 Print only waypoints which have an elevation.
1189 Print only waypoints which have a position.
1191 Print only waypoints which have a timestamp.
1192 -R, --round x=y[,x2=y2[...]]
1193 Round trackpoint element x to y decimals. Example:
1194 --round lat=4,lon=5,ele=1
1195 -s, --short-date
1196 Use short date format.
1197 -S, --save-to-file x
1198 Save the unconverted data to a file with a filename starting with
1199 the timestamp of the first trackpoint. The parameter string x is
1200 added at the end of the filename. For the time being this option
1201 will ignore all other options. Note: If several files are specified
1202 on the command line, all data will be saved into only one file. This
1203 behaviour may change in the future.
1204 -t, --create-breaks
1205 Create breaks in track between points with a difference more than
1206 $PAUSE_LIMIT seconds.
1207 -T x, --time-shift x
1208 Move time of trackpoint x seconds forwards or backwards. x can be a
1209 positive or negative integer.
1210 -v, --verbose
1211 Increase level of verbosity. Can be repeated.
1212 --version
1213 Print version information.
1214 -w, --strip-whitespace
1215 Strip all unnecessary whitespace.
1216 -y, --double-y-scale
1217 Double Y scale (latitude) to get it right in gnuplot.
1218 --debug
1219 Print debugging messages.
1222 exit($Retval);
1223 # }}}
1224 } # usage()
1226 sub msg {
1227 # Print a status message to stderr based on verbosity level {{{
1228 my ($verbose_level, $Txt) = @_;
1230 if ($Opt{'verbose'} >= $verbose_level) {
1231 print(STDERR "$progname: $Txt\n");
1233 # }}}
1234 } # msg()
1236 __END__
1238 # Law talk {{{
1239 # Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1241 This program is free software: you can redistribute it and/or modify it
1242 under the terms of the GNU General Public License as published by the
1243 Free Software Foundation, either version 3 of the License, or (at your
1244 option) any later version.
1246 This program is distributed in the hope that it will be useful, but
1247 WITHOUT ANY WARRANTY; without even the implied warranty of
1248 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1249 See the GNU General Public License for more details.
1251 You should have received a copy of the GNU General Public License along
1252 with this program.
1253 If not, see L<http://www.gnu.org/licenses/>.
1254 # }}}
1256 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :