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