quiet-gpsman2gpx.patch som lå på ww. Fildatoen er 2006-12-15 19:33:13.
[gpstools.git] / gpst
blob55634a1a6c8254fcf4aa72f6081b0b7cf550f409
1 #!/usr/bin/perl -w
3 #=======================================================================
4 # $Id$
5 # Converts between various GPS formats
7 # Character set: UTF-8
8 # ©opyleft 2002– Øyvind A. Holm <sunny@sunbase.org>
9 # License: GNU General Public License version 2 or later, see end of
10 # file for legal stuff.
11 #=======================================================================
13 BEGIN {
14 our @version_array;
17 use strict;
18 use Getopt::Long;
19 use Time::Local qw { timegm_nocheck };
21 BEGIN {
22 push(@INC, "$ENV{'HOME'}/bin/src/gpstools");
23 our @version_array;
26 use GPST;
27 use GPSTdate;
28 use GPSTdebug;
29 use GPSTgeo;
30 use GPSTxml;
32 $| = 1;
34 our $Debug = 0;
36 our %Opt = (
37 # Initial values for command line arguments {{{
39 'chronology' => 0,
40 'create-breaks' => 0,
41 'debug' => 0,
42 'double-y-scale' => 0,
43 'epoch' => 0,
44 'fix' => 0,
45 'from-date' => "",
46 'help' => 0,
47 'inside' => 0,
48 'near' => "",
49 'output-format' => "gpsml",
50 'outside' => 0,
51 'pos1' => "",
52 'pos2' => "",
53 'require' => "",
54 'round' => "",
55 'save-to-file' => "\n", # \n = undefined, it’s banned in filenames anyway.
56 'short-date' => 0,
57 'skip-dups' => 0,
58 'strip-whitespace' => 0,
59 'time-shift' => 0,
60 'undefined' => "",
61 'verbose' => 0,
62 'version' => 0,
64 # }}}
67 our $progname = $0;
68 $progname =~ s/^.*\/(.*?)$/$1/;
70 my $rcs_id = '$Id$';
71 my $id_date = $rcs_id;
72 $id_date =~ s/^.*?\d+ (\d\d\d\d-.*?\d\d:\d\d:\d\d\S+).*/$1/;
74 push(@main::version_array, $rcs_id);
76 Getopt::Long::Configure("bundling");
77 GetOptions(
78 # Command line options {{{
80 "chronology" => \$Opt{'chronology'},
81 "create-breaks|t" => \$Opt{'create-breaks'},
82 "debug" => \$Opt{'debug'},
83 "double-y-scale|y" => \$Opt{'double-y-scale'},
84 "epoch|e" => \$Opt{'epoch'},
85 "fix" => \$Opt{'fix'},
86 "from-date=s" => \$Opt{'from-date'},
87 "help|h" => \$Opt{'help'},
88 "inside" => \$Opt{'inside'},
89 "near" => \$Opt{'near'},
90 "output-format|o=s" => \$Opt{'output-format'},
91 "outside" => \$Opt{'outside'},
92 "pos1=s" => \$Opt{'pos1'},
93 "pos2=s" => \$Opt{'pos2'},
94 "require|r=s" => \$Opt{'require'},
95 "round|R=s" => \$Opt{'round'},
96 "save-to-file|S=s" => \$Opt{'save-to-file'},
97 "short-date|s" => \$Opt{'short-date'},
98 "skip-dups|d" => \$Opt{'skip-dups'},
99 "strip-whitespace|w" => \$Opt{'strip-whitespace'},
100 "time-shift|T=i" => \$Opt{'time-shift'},
101 "undefined|n=s" => \$Opt{'undefined'},
102 "verbose|v+" => \$Opt{'verbose'},
103 "version" => \$Opt{'version'},
105 # }}}
106 ) || die("$progname: Option error. Use -h for help.\n");
108 my %Dat;
110 my $PAUSE_LIMIT = 2 * 60; # Antall sekunder mellom to punkter det må til før en move legges inn.
111 my $Udef = "?";
112 my $DIGIT = '[0-9\.\-\+]'; # Used in regexps
113 $GPST::Spc = $Opt{'strip-whitespace'} ? "" : " ";
114 my $Spc = $GPST::Spc; # FIXME
115 my $found_move = 0; # Settes til 1 hvis en /^# move$/ blir funnet.
116 my $first_time = 0;
117 my $last_time = 0;
118 my ($last_lon, $last_lat, $last_line) =
119 ( 1000, 1000, ""); # Vi kan jo teoretisk sett være i Greenwich eller på ekvator
120 my ($lat1, $lon1, $lat2, $lon2) =
121 (-1000, -1000, 1000, 1000);
123 our %Cmd = (
124 'gpsbabel' => '/usr/local/bin/gpsbabel',
127 my %Poscount = ();
129 if ($Opt{'output-format'} eq "pgtab") {
130 $Opt{'require'} .= "p";
132 my %Req = (
133 'ele' => ($Opt{'require'} =~ /e/) ? 1 : 0,
134 'position' => ($Opt{'require'} =~ /p/) ? 1 : 0,
135 'time' => ($Opt{'require'} =~ /t/) ? 1 : 0,
137 $Opt{'require'} =~ /[^ept]/
138 && die("$0: Unknown flag in --require (-r) value\n");
140 $Opt{'debug'} && ($Debug = 1);
141 $Opt{'help'} && usage(0);
142 if ($Opt{'version'}) {
143 print_version();
144 exit(0);
147 if ($Opt{'pos1'} =~ /^($DIGIT+),($DIGIT+)$/) {
148 $lat1 = $1;
149 $lon1 = $2;
151 if ($Opt{'pos2'} =~ /^($DIGIT+),($DIGIT+)$/) {
152 $lat2 = $1;
153 $lon2 = $2;
155 if ($lat1 > $lat2) {
156 my $Tmp = $lat1;
157 $lat1 = $lat2;
158 $lat2 = $Tmp;
160 if ($lon1 > $lon2) {
161 my $Tmp = $lon1;
162 $lon1 = $lon2;
163 $lon2 = $Tmp;
166 if ($Opt{'epoch'} && $Opt{'short-date'}) {
167 die("$progname: Cannot mix the --epoch (-e) and --short-date (-s) options\n");
170 if ($Opt{'inside'} && $Opt{'outside'}) {
171 die("$progname: Cannot mix the --inside and --outside options\n");
174 # To avoid printing out extra "/> at the start of svg output:
175 my $svg_start_thing = "";
177 my %Round = ();
179 if (defined($Opt{'round'})) {
180 my $R = $Opt{'round'};
181 $R =~ s/([a-z]+)=(\d+)/($Round{$1}=$2, "")/eg;
184 length($Opt{'undefined'}) && ($Udef = $Opt{'undefined'});
186 $Opt{'save-to-file'} eq "\n" && print_header(*STDOUT);
188 my @first_lines;
189 my $xml_data;
190 my $data_line = "";
191 our $curr_file = "";
193 my $from_stdin = scalar(@ARGV) ? 0 : 1;
195 $from_stdin && push(@ARGV, "-");
197 for $curr_file (@ARGV) {
198 # Scan through stdin or specified files and send every GPS entry to
199 # print_entry()
200 # {{{
201 print(STDERR "$progname: Opening \"$curr_file\" for read\n") if $Opt{'verbose'};
202 if (open(CurrFP, "<$curr_file")) {
203 # {{{
204 while (<CurrFP>) {
205 $data_line = $_;
206 %Dat = (
207 'year' => '', 'month' => '', 'day' => '',
208 'hour' => '', 'min' => '', 'sec' => '',
209 'epoch' => '',
210 'date-format' => '',
211 'lat' => '', 'lon' => '',
212 'ele' => '',
213 'desc' => '',
214 'error' => "",
215 'what' => 'tp',
218 $Opt{'epoch'} && ($Dat{'date-format'} = "epoch");
219 $Opt{'short-date'} && ($Dat{'date-format'} = "short");
221 if ($Opt{'save-to-file'} ne "\n") {
222 push(@first_lines, $_);
224 s/^# error // && ($Dat{'error'} = "error");
225 s/^# ?// && ($Dat{'error'} = "desc");
226 $xml_data = "";
227 if (m#^<(e?tp)\b(.*?)>(.*?)</(e?tp)>\s*$#) {
228 # gpsml — The main storage format {{{
229 my ($Elem, $Props, $Data) =
230 ( $1, $2, $3);
231 my $err_str = ($Props =~ /\berr="(.*?)"/) ? $1 : "error";
232 $Elem eq "etp" && ($Dat{'error'} = $err_str);
233 my $Time = "";
234 $Data =~ m#<time>(.*?)</time># && ($Time = $1);
235 $Time =~ s{
236 (\d\d\d\d)-?(\d\d)-?(\d\d)[T ](\d\d):?(\d\d):?([\d\.]+?)Z
238 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
239 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
240 ( $1, $2, $3,
241 $4, $5, $6);
243 }ex;
244 $Data =~ m#<lat>($DIGIT*?)</lat># && ($Dat{'lat'} = $1);
245 $Data =~ m#<lon>($DIGIT*?)</lon># && ($Dat{'lon'} = $1);
246 $Data =~ m#<ele>($DIGIT*?)</ele># && ($Dat{'ele'} = $1);
247 $Data =~ m#<desc>(.*?)</desc># && ($Dat{'desc'} = $1);
248 print_entry(%Dat);
249 # }}}
250 } elsif (m#^<break\b.*?/>#) {
251 $found_move = 1;
252 } elsif (m#^<(title|pause)\b.*?>(.*?)</(title|pause)>#) {
253 $Dat{'what'} = $1;
254 $Dat{$1} = $2;
255 print_entry(%Dat);
256 } elsif (m#^<desc\b.*?>(.*$)#s) {
257 $Dat{'what'} = "desc";
258 my $Txt = $1;
259 until ($Txt =~ m#</desc>#s) {
260 $Txt .= <CurrFP>;
262 $Txt =~ s#^(.*)(</desc>.*$)#$1#s;
263 $Dat{'desc'} = $Txt;
264 print_entry(%Dat);
265 } elsif (/<gpx\b/) {
266 $xml_data = $_;
267 $xml_data .= join("", <CurrFP>);
268 if (!length($Opt{'output-format'})) {
269 $Opt{'output-format'} = "gpx";
270 print_header(*STDOUT);
272 read_xmlfile($xml_data);
273 last;
274 } elsif (/^move$/) {
275 $found_move = 1;
276 } elsif (m#^(\d+)\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
277 # CSV format, epoch style {{{
278 my ($ep_time, $lon_val, $lat_val, $Alt) =
279 ( $1, $2, $3, $4);
280 $Dat{'epoch'} = $ep_time;
281 ($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
282 $Dat{'day'}, $Dat{'month'}, $Dat{'year'},
283 $Dat{'wday'}, $Dat{'yday'}) = gmtime($ep_time);
284 $Dat{'month'}++; # Urgh Ⅰ
285 $Dat{'year'} += 1900; # Urgh Ⅱ
286 print_entry(%Dat);
287 # }}}
288 } elsif (
290 (\d\d\d\d)-?(\d\d)-?(\d\d)[T\ ](\d\d):?(\d\d):?(\d\d)Z?\t
291 ($DIGIT+)\t($DIGIT+)\t($DIGIT)
294 # CSV format, human-readable date format {{{
295 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
296 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
297 $Dat{'lon'}, $Dat{'lat'}, $Dat{'ele'}) =
298 ($1, $2, $3,
299 $4, $5, $6,
300 $7, $8, $9);
301 print_entry(%Dat);
302 # }}}
303 } elsif (/^Trackpoint\t/) {
304 # 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 {{{
306 # Trackpoint\t
307 # N60.41630 E5.31675\t
308 # 09.02.2006 20:24:37 (UTC)\t
309 # 13.6 m\t
310 # \t
311 # 93.9 m\t
312 # 00:00:06\t
313 # 56 kph\t
314 # 123° true
315 my $Orig = $_;
316 $Orig =~ s/[\r\n]+$//;
317 my ($Marker_f, $Position_f, $Time_f, $Alt_f, $Depth_f,
318 $Leglength_f, $Legtime_f, $Legspeed_f, $Legcourse_f) =
319 split(/\t/, $Orig .
320 # Nødløsning for å unngå at variabler
321 # blir udefinert.
322 "\t\t\t\t\t\t\t\t\t\t"
324 D(join("",
325 "Position_f=\"$Position_f\" \x7B\x7B\x7B\n",
326 "Time_f=\"$Time_f\"\n",
327 "Alt_f=\"$Alt_f\"\n",
328 "Depth_f=\"$Depth_f\"\n",
329 "Leglength_f=\"$Leglength_f\"\n",
330 "Legtime_f=\"$Legtime_f\"\n",
331 "Legspeed_f=\"$Legspeed_f\"\n",
332 "Legcourse_f=\"$Legcourse_f\" \x7D\x7D\x7D\n",
334 my ($NS, $WE,
335 $Alt_unit,
336 $Leglength,
337 $Legtime_hour, $Legtime_min, $Legtime_sec,
338 $Legspeed, $Legspeed_unit,
339 $Legcourse
340 ) = ("", "", "", "", "", "", "", "", "", "", "", "", "",
341 "", "", "", "", "", "", "", "", "", "");
342 ($Position_f =~ /^(N|S)([\d\.]+) (W|E)([\d\.]+)/) &&
343 ($NS = $1, $Dat{'lat'} = $2, $WE = $3, $Dat{'lon'} = $4);
344 ($Time_f =~ /^(\d+)\.(\d+)\.(\d+) (\d+):(\d+):(\d+) \((.+?)\)/) &&
345 ($Dat{'day'} = $1, $Dat{'month'} = $2, $Dat{'year'} = $3,
346 $Dat{'hour'} = $4, $Dat{'min'} = $5, $Dat{'sec'} = $6);
347 ($Alt_f =~ /^($DIGIT+) (.*?)/) &&
348 ($Dat{'ele'} = $1, $Alt_unit = $2);
349 D("ele = \"$Dat{'ele'}\"");
350 ($NS eq "S") && ($Dat{'lat'} = 0-$Dat{'lat'});
351 ($WE eq "W") && ($Dat{'lon'} = 0-$Dat{'lon'});
352 # MapSource in win xp writes YYYY, but YY in win98se.
354 defined($Dat{'year'})
355 && $Dat{'year'} =~ /\d/
356 && $Dat{'year'} < 1900
357 ) && ($Dat{'year'} += 2000);
358 print_entry(%Dat);
359 # }}}
360 } elsif (/^Track\t(.*?)\t/) {
361 $Dat{'title'} = txt_to_xml($1);
362 $Dat{'what'} = "title";
363 $found_move = 1;
364 print_entry(%Dat);
365 } elsif (
368 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)\t
369 (.+)\xB0(.+)'(.+)"\t
370 (.+)\xB0(.+)'(.+)"
373 # T 09/01/2002 11:51:26 60°23'36.3" 5°19'35.9" {{{
374 my ($lat_d, $lat_m, $lat_s, $lon_d, $lon_m, $lon_s);
375 ($Dat{'month'}, $Dat{'day'}, $Dat{'year'},
376 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
377 $lat_d, $lat_m, $lat_s,
378 $lon_d, $lon_m, $lon_s) =
379 ($1, $2, $3,
380 $4, $5, $6,
381 $7, $8, $9,
382 $10, $11, $12);
383 my $Flat = defined($Round{'lat'}) ? ".$Round{'lat'}" : "";
384 my $Flon = defined($Round{'lon'}) ? ".$Round{'lon'}" : "";
385 $Dat{'lat'} = sprintf("%${Flat}f",
386 1.0*($lat_d+($lat_m/60)+($lat_s/3600)));
387 $Dat{'lon'} = sprintf("%${Flon}f",
388 1.0*$lon_d+($lon_m/60)+($lon_s/3600));
389 print_entry(%Dat);
390 # }}}
391 } elsif (
393 1\ (\S+)\ (\S+)\ (\S+)\ (\S+)\x20
394 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)
397 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
398 ($Dat{'lat'}, $Dat{'lon'}, $Dat{'speed'},
399 $Dat{'unkn'},
400 $Dat{'month'}, $Dat{'day'}, $Dat{'year'},
401 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
402 ($1, $2, $3,
404 $5, $6, $7,
405 $8, $9, $10);
406 print_entry(%Dat);
407 # }}}
408 } elsif (/^
409 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
410 # Regexp {{{
411 (@) # @
412 (\d\d) # Year
413 (\d\d) # Month
414 (\d\d) # Day
415 (\d\d) # Hours
416 (\d\d) # Minutes
417 (\d\d) # Seconds
418 ([NS]) # N|S
419 (\d\d) # Latitude degree
420 (\d\d) # Latitude minute
421 (\d\d\d) # Latitude minute decimals
422 ([EW]) # E|W
423 (\d\d\d) # Longitude degree
424 (\d\d) # Longitude minute
425 (\d\d\d) # Longitude minute degree
426 (....) # Accurancy
427 (......) # Elevation
428 (...............)
429 # }}}
430 /x) {
431 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
432 $lon_degmin, $lon_mindec);
433 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'}, $Dat{'hour'},
434 $Dat{'min'}, $Dat{'sec'}, $NS, $lat_deg,
435 $lat_degmin, $lat_mindec, $EW,
436 $lon_deg, $lon_degmin, $lon_mindec,
437 $Dat{'accur'}, $Dat{'ele'}, $Dat{'unknown'}) =
438 ($2+2000, $3, $4, $5,
439 $6, $7, $8, $9,
440 $10, $11, $12,
441 $13, $14, $15,
442 $16, $17, $18);
443 my $ep_time = timegm_nocheck(
444 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
445 $Dat{'day'}, $Dat{'month'}-1, $Dat{'year'}
447 $last_time = $ep_time;
448 my $Flat = defined($Round{'lat'}) ? ".$Round{'lat'}" : "";
449 my $Flon = defined($Round{'lon'}) ? ".$Round{'lon'}" : "";
450 my $tmp_lon = sprintf(
451 "%${Flon}f",
452 $lon_deg +
453 $lon_degmin/60 +
454 $lon_mindec/60000);
455 my $tmp_lat = sprintf("%${Flat}f",
456 $lat_deg +
457 $lat_degmin/60 +
458 $lat_mindec/60000);
459 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
460 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
461 $Dat{'lat'} = $tmp_lat;
462 $Dat{'lon'} = $tmp_lon;
463 print_entry(%Dat);
464 # }}}
465 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(_{42})/) {
466 # @020721221336__________________________________________ {{{
467 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
468 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $Dat{'rest'}) =
469 ($2+2000, $3, $4,
470 $5, $6, $7, $8);
471 $Dat{'error'} = "nosignal";
472 print_entry(%Dat);
473 # }}}
474 } elsif (/^xmaplog /) {
475 # NOP
476 } elsif (/^$/) {
477 ($Opt{'output-format'} eq "csv")
478 && ($Opt{'save-to-file'} eq "\n")
479 && print("\n");
480 } elsif (/^Pause: /) {
481 # NOP, is here to cope with old files I’ve lying around.
482 } elsif ($Dat{'error'} eq "desc") {
483 my $Comment = $_;
484 if (defined($Comment)) {
485 $Comment =~ s/^\s*(.*?)\s*$/$1/;
486 if ($Opt{'output-format'} eq "gpsml") {
487 $Dat{'desc'} = txt_to_xml($Comment);
488 $Dat{'what'} = "desc";
489 print_entry(%Dat);
492 } else {
493 $Opt{'verbose'} && warn("Line $.: Unknown: \"$_\"\n");
496 # }}}
497 } else {
498 warn("$progname: $curr_file: Cannot open file for read: $!\n");
500 # }}}
503 print_footer(*STDOUT);
505 exit(0);
507 sub read_xmlfile {
508 # {{{
509 my $Txt = join("", @_);
510 $Txt =~ s/<!--(.*?)-->//gs;
511 $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
512 # }}}
515 sub print_gpx {
516 # {{{
517 my $Orig = shift;
518 my $Str = $Orig;
519 # D("print_xml_gps(\"$Orig\")\n");
520 $Str =~ s/<!--(.*?)-->//gs;
521 my $fromdate_str = "";
522 if ($Opt{'from-date'}) {
523 $fromdate_str = "date >= '$Opt{'from-date'}' AND ";
525 if ($Opt{'output-format'} =~ /^(pgwtab|pgwupd)$/) {
526 # {{{
527 $Str =~
529 <wpt\b(.*?)>(.*?)</wpt>
532 my $attr_wpt = $1;
533 my $el_wpt = $2;
534 my ($Lat, $Lon, $Name, $Ele, $Type, $Time, $Cmt, $Desc, $Src, $Sym) =
535 ('\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N');
537 $attr_wpt =~ /.*lat="($DIGIT+?)"/s &&
538 ($Lat = postgresql_copy_safe($1));
539 $attr_wpt =~ /.*lon="($DIGIT+?)"/s &&
540 ($Lon = postgresql_copy_safe($1));
541 $el_wpt =~ /.*<name\b(.*?)>(.*?)<\/name>/s &&
542 ($Name = postgresql_copy_safe(xml_to_txt($2)));
543 $el_wpt =~ /.*<ele\b(.*?)>(.*?)<\/ele>/s &&
544 ($Ele = postgresql_copy_safe(xml_to_txt($2)));
545 $el_wpt =~ /.*<type\b(.*?)>(.*?)<\/type>/s &&
546 ($Type = postgresql_copy_safe(xml_to_txt($2)));
547 $el_wpt =~ /.*<time\b(.*?)>(.*?)<\/time>/s &&
548 ($Time = postgresql_copy_safe(xml_to_txt($2)));
549 $el_wpt =~ /.*<cmt\b(.*?)>(.*?)<\/cmt>/s &&
550 ($Cmt = postgresql_copy_safe(xml_to_txt($2)));
551 $el_wpt =~ /.*<desc\b(.*?)>(.*?)<\/desc>/s &&
552 ($Desc = postgresql_copy_safe(xml_to_txt($2)));
553 $el_wpt =~ /.*<src\b(.*?)>(.*?)<\/src>/s &&
554 ($Src = postgresql_copy_safe(xml_to_txt($2)));
555 $el_wpt =~ /.*<sym\b(.*?)>(.*?)<\/sym>/s &&
556 ($Sym = postgresql_copy_safe(xml_to_txt($2)));
558 if (length($Opt{'round'})) {
559 if (defined($Round{'lat'}) && length($Lat)) {
560 ($Lat = 1.0 * sprintf("%.$Round{'lat'}f", $Lat));
562 if (defined($Round{'lon'}) && length($Lon)) {
563 ($Lon = 1.0 * sprintf("%.$Round{'lon'}f", $Lon));
565 if (defined($Round{'ele'}) && $Ele ne '\N') {
566 ($Ele = 1.0 * sprintf("%.$Round{'ele'}f", $Ele));
570 if ($Opt{'output-format'} eq "pgwtab") {
571 print(
572 join("\t",
573 "($Lat,$Lon)",
574 $Name,
575 $Ele,
576 $Type,
577 $Time,
578 $Cmt,
579 $Desc,
580 $Src,
581 $Sym
582 ) . "\n"
584 } elsif ($Opt{'output-format'} eq "pgwupd") {
585 $Name =~ s/'/''/gs;
586 print(join("\n",
587 "BEGIN;",
588 "$Spc${Spc}UPDATE logg SET sted = clname(coor) " .
589 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
590 "$Spc${Spc}UPDATE logg SET dist = cldist(coor) " .
591 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
592 "COMMIT;"
593 ) . "\n");
596 }gsex;
597 # }}}
598 } else {
599 # {{{
600 $Str =~
602 <trk\b(.*?)>(.*?)</trk>
605 my $el_trk = $2;
606 $el_trk =~
608 <name\b(.*?)>(.*?)</name>
610 my %tmp_dat = ();
611 $tmp_dat{'title'} = $2;
612 $tmp_dat{'what'} = "title";
613 $tmp_dat{'error'} = "";
614 print_entry(%tmp_dat);
616 }sex;
617 $el_trk =~
619 <trkseg\b(.*?)>(.*?)</trkseg>
622 my $el_trkseg = $2;
623 $el_trkseg =~
625 <trkpt\b(.*?)>(.*?)</trkpt>
628 my ($attr_trkpt, $el_trkpt) =
629 ( $1, $2);
630 %Dat = (
631 'year' => '', 'month' => '', 'day' => '',
632 'hour' => '', 'min' => '', 'sec' => '',
633 'epoch' => '',
634 'date-format' => '',
635 'lat' => '', 'lon' => '',
636 'ele' => '',
637 'desc' => '',
638 'error' => "",
639 'what' => 'tp',
641 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon'} = $1);
642 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat'} = $1);
643 ($el_trkpt =~ m#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele'} = $1);
644 if (
645 $el_trkpt =~
647 <time>(\d\d\d\d)-?(\d\d)-?(\d\d)T
648 (\d\d):?(\d\d):?([\d\.]+)Z</time>
651 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
652 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
653 ($1, $2, $3, $4, $5, $6);
655 print_entry(%Dat);
657 }gsex;
658 $found_move = 1;
659 }gsex;
660 $found_move = 1;
661 }gsex;
662 # }}}
664 # }}}
667 sub print_header {
668 # {{{
669 local *OutFP = shift;
670 if ($Opt{'output-format'} eq "gpsml") {
671 print(OutFP join("",
672 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
673 "<gpsml>\n",
674 "<track>\n",
676 } elsif ($Opt{'output-format'} eq "gpstrans") {
677 print(OutFP "Format: DMS UTC Offset: 0.00 hrs " .
678 "Datum[100]: WGS 84\n");
679 } elsif ($Opt{'output-format'} eq "gpx") {
680 print(OutFP join("",
681 qq{<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n},
682 qq{<gpx\n},
683 qq{$Spc${Spc}version="1.1"\n},
684 qq{$Spc${Spc}creator="gpst - http://svn.sunbase.org/repos/utils/trunk/src/gpstools/"\n},
685 qq{$Spc${Spc}xmlns="http://www.topografix.com/GPX/1/1"\n},
686 qq{$Spc${Spc}xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n},
687 qq{$Spc${Spc}xsi:schemaLocation="http://www.topografix.com/GPX/1/1 },
688 qq{http://www.topografix.com/GPX/1/1/gpx.xsd"\n},
689 qq{>\n},
690 qq{$Spc$Spc<trk>\n},
691 qq{$Spc$Spc$Spc$Spc<trkseg>\n},
693 } elsif ($Opt{'output-format'} eq "ps") {
694 print(OutFP ps_header(532, 6034, 533, 6040));
695 print(OutFP "*u\n");
696 } elsif ($Opt{'output-format'} eq "svg") {
697 print(OutFP join("",
698 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
699 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
700 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
701 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
702 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
703 "$Spc$Spc<title></title>\n",
704 "$Spc$Spc<desc></desc>\n",
707 # }}}
710 sub print_footer {
711 # Print footer {{{
712 local *OutFP = shift;
713 if ($Opt{'output-format'} eq "gpsml") {
714 print(OutFP join("",
715 "</track>\n",
716 "</gpsml>\n",
718 } elsif ($Opt{'output-format'} eq "gpx") {
719 print(OutFP join("",
720 "$Spc$Spc$Spc$Spc</trkseg>\n",
721 "$Spc$Spc</trk>\n",
722 "</gpx>\n",
724 } elsif ($Opt{'output-format'} eq "poscount") {
725 while (my ($l_name, $l_val) = each %Poscount) {
726 $l_name =~ /^(.+?),(.+?)$/
727 && print(OutFP "$1\t$2\t$l_val\n");
729 } elsif ($Opt{'output-format'} eq "ps") {
730 print(OutFP join("",
731 "*U\n",
732 "%%Trailer\n",
733 "%%EOF\n",
735 } elsif ($Opt{'output-format'} eq "svg") {
736 print(OutFP "\"/>\n</svg>\n");
738 # }}}
741 sub print_entry {
742 # Print a GPS entry with time, latitude, longitude and elevation in
743 # various formats
744 # {{{
745 my %Dat = @_;
746 defined($Dat{'desc'}) || ($Dat{'desc'} = "");
747 defined($Dat{'ele'}) || ($Dat{'ele'} = "");
748 defined($Dat{'lat'}) || ($Dat{'lat'} = "");
749 defined($Dat{'lon'}) || ($Dat{'lon'} = "");
750 defined($Dat{'year'}) || ($Dat{'year'} = "");
751 my $print_time = length($Dat{'year'}) ? 1 : 0;
752 my $print_pos;
753 if (!$Req{'position'} && $Opt{'output-format'} eq "gpsml") {
754 $print_pos = (length($Dat{'lat'}) || length($Dat{'lon'})) ? 1 : 0;
755 } else {
756 $print_pos = (length($Dat{'lat'}) && length($Dat{'lon'})) ? 1 : 0;
758 if (!$print_pos) {
759 $Dat{'lat'} = $Dat{'lon'} = "";
761 my $print_ele = length($Dat{'ele'}) ? 1 : 0;
762 my $print_desc = length($Dat{'desc'}) ? 1 : 0;
763 my $Line = "";
764 D("print_entry(\"" . join("\", \"", @_) . "\");");
765 my $ep_time;
767 if ($Opt{'near'} && $print_pos) {
768 $Line .= sprintf("%s ",
769 list_nearest_waypoints($Dat{'lat'}, $Dat{'lon'}));
772 if (length($Opt{'round'})) {
773 for my $Tmp (qw{ lat lon ele }) {
774 if (defined($Round{$Tmp}) && length($Dat{$Tmp})) {
775 D("Tmp = '$Tmp'");
776 ($Dat{$Tmp} = 1.0 * sprintf("%.$Round{$Tmp}f", $Dat{$Tmp}));
781 if ($Opt{'output-format'} eq "poscount") {
782 if (!length($Dat{'error'})) {
783 my $Name = "$Dat{'lon'},$Dat{'lat'}";
784 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
785 $Poscount{$Name}++;
787 return;
790 if ($print_time) {
791 $ep_time = timegm_nocheck(
792 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
793 $Dat{'day'}, $Dat{'month'} - 1, $Dat{'year'}
795 if ($Opt{'time-shift'}) {
796 D("ep_time før: '$ep_time'");
797 $ep_time += $Opt{'time-shift'};
798 D("ep_time etter: '$ep_time'");
799 ($Dat{'sec'}, $Dat{'min'},$Dat{'hour'}, $Dat{'day'},
800 $Dat{'month'}, $Dat{'year'}) = gmtime($ep_time);
801 $Dat{'year'} += 1900;
802 $Dat{'month'}++;
804 $Dat{'epoch'} = $ep_time;
805 $Dat{'year'} = sprintf("%04u", $Dat{'year'});
806 $Dat{'month'} = sprintf("%02u", $Dat{'month'});
807 $Dat{'day'} = sprintf("%02u", $Dat{'day'});
808 $Dat{'hour'} = sprintf("%02u", $Dat{'hour'});
809 $Dat{'min'} = sprintf("%02u", $Dat{'min'});
810 $Dat{'sec'} = sprintf("%02u", $Dat{'sec'});
811 if ($Opt{'chronology'}) {
812 if ($last_time > $ep_time && !length($Dat{'error'})) {
813 warn(sprintf(
814 "%s: $curr_file: \"%sZ\": Next date is %s in the past (%sZ)\n",
815 $progname, sec_to_string($last_time, "T"),
816 sec_to_readable($last_time-$ep_time),
817 sec_to_string($ep_time, "T")
819 # FIXME: Make --fix work with gpx.
820 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
821 $Dat{'error'} = "chrono";
823 } elsif ($last_time == $ep_time && !length($Dat{'error'})) {
824 warn(sprintf(
825 "%s: $curr_file: \"%sZ\": Duplicated time\n",
826 $progname, sec_to_string($last_time, "T")
828 # FIXME: Make --fix work with gpx.
829 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
830 $Dat{'error'} = "duptime";
834 } else {
835 $ep_time = 0;
836 $Dat{'year'} = 0;
837 $Dat{'month'} = 0;
838 $Dat{'day'} = 0;
839 $Dat{'hour'} = 0;
840 $Dat{'min'} = 0;
841 $Dat{'sec'} = 0;
844 if ($Opt{'save-to-file'} ne "\n") {
845 # {{{
846 $print_time || return;
847 my $base_name = "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
848 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z" .
849 "$Opt{'save-to-file'}";
850 my $file_name = $base_name;
851 if (-e $file_name) {
852 for (my $a = 1; (-e $file_name) && ($a < 1000); $a++) {
853 $file_name = "$base_name.dup_$a";
855 if (-e $file_name) {
856 die("$progname: $base_name: File already exists, and ran " .
857 "out of attempts to create unique file name\n");
859 if ($Opt{'verbose'}) {
860 warn("$progname: $base_name: File already exists, using " .
861 "unique name \"$file_name\" instead\n");
864 if (open(ToFP, ">", $file_name)) {
865 print_header(*ToFP);
866 print(ToFP (
867 $from_stdin
868 ? @first_lines
869 : ()),
870 (length($xml_data)
871 ? $xml_data
872 : <>)
873 ) || die("$progname: $file_name: Cannot write to file: $!\n");
874 print_footer(*ToFP);
875 close(ToFP);
876 if ($Opt{'output-format'} eq "gpsml") {
877 printf("<include>%s</include>\n",
878 txt_to_xml($file_name));
879 } elsif ($Opt{'output-format'} eq "gpx") {
880 printf("<!-- Saved unconverted data to \"%s\" -->\n",
881 txt_to_xml($file_name));
882 } else {
883 print("$progname: Saved unconverted data to \"$file_name\"\n");
885 exit 0;
886 } else {
887 die("$progname: $file_name: Cannot create file: $!\n");
889 # }}}
892 my $pause_len = 0;
893 my $do_print = 1;
895 if ($Dat{'what'} eq "tp") {
896 # {{{
897 if ($Opt{'require'}) {
898 $Req{'time'} && !$print_time && return;
899 $Req{'position'} && !$print_pos && return;
900 $Req{'ele'} && !$print_ele && return;
903 if ($Opt{'inside'} || $Opt{'outside'}) {
904 if (
905 ($Dat{'lat'} < $lat1) ||
906 ($Dat{'lat'} > $lat2) ||
907 ($Dat{'lon'} < $lon1) ||
908 ($Dat{'lon'} > $lon2)
910 $Opt{'inside'} && return;
911 } else {
912 $Opt{'outside'} && return;
916 if ($Opt{'output-format'} eq "ps") {
917 $Dat{'lon'} *= 100;
918 $Dat{'lat'} *= 100;
921 if (
922 $Opt{'skip-dups'}
923 && ($Dat{'lon'} eq $last_lon)
924 && ($Dat{'lat'} eq $last_lat)
926 if ($Opt{'output-format'} eq 'gpsml') {
927 $Dat{'error'} = "dup";
928 } else {
929 $do_print = 0;
931 } else {
932 $do_print = 1;
935 if (
936 $Opt{'create-breaks'}
937 && $ep_time-$last_time > $PAUSE_LIMIT
938 && $last_time
940 $pause_len = $ep_time-$last_time;
941 D("pause_len set to '$pause_len'");
944 if ($pause_len) {
945 if ($Opt{'output-format'} eq "gpsml") {
946 $Line .= sprintf("<pause>%s</pause>\n",
947 sec_to_readable($ep_time-$last_time));
948 } elsif ($Opt{'output-format'} eq "clean") {
949 $pause_len && ($Line .= "\n");
950 } elsif ($Opt{'output-format'} eq "csv") {
951 $Line .= sprintf("# Pause: %s\n# move\n",
952 sec_to_readable($ep_time-$last_time));
953 } elsif ($Opt{'output-format'} eq "xgraph") {
954 $pause_len && ($Line .= "move ");
957 # }}}
960 if ($do_print) {
961 # Valid data was found, send to stdout {{{
962 unless ($first_time) {
963 $first_time = $ep_time;
965 if ($Opt{'double-y-scale'} && length($Dat{'lat'})) {
966 $Dat{'lat'} *= 2;
968 if ($Opt{'output-format'} eq "gpsml") {
969 if ($Dat{'what'} eq "tp") {
970 $Dat{'format'} = "gpsml";
971 $Line .= trackpoint(%Dat);
972 } elsif ($Dat{'what'} =~ /^(pause|desc|title)$/) {
973 $Line .= sprintf("<%s>%s</%s>\n",
975 $Dat{$1},
976 $1);
978 } elsif ($Opt{'output-format'} eq "pgtab") {
979 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
980 $Dat{'format'} = "pgtab";
981 $Line .= trackpoint(%Dat);
983 } elsif ($Opt{'output-format'} eq "xgraph") {
984 if ($print_pos && !length($Dat{'error'})) {
985 $Dat{'format'} = "xgraph";
986 $Line .= trackpoint(%Dat);
988 } elsif($Opt{'output-format'} eq "gpstrans") {
989 if ($print_pos && !length($Dat{'error'})) {
990 $Dat{'format'} = "gpstrans";
991 $Line .= trackpoint(%Dat);
993 } elsif($Opt{'output-format'} eq "gpx") {
994 if ($Dat{'what'} eq "tp") {
995 $Dat{'format'} = "gpx";
996 $Line .= trackpoint(%Dat);
998 } elsif ($Opt{'output-format'} eq "clean") {
999 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
1000 $Dat{'format'} = "clean";
1001 $Line .= trackpoint(%Dat);
1003 } elsif ($Opt{'output-format'} eq "ps") {
1004 $Line .= (
1005 $pause_len
1006 ? "f\n$Dat{'lon'} $Dat{'lat'} m\n"
1007 : "$Dat{'lon'} $Dat{'lat'} l\n"
1009 } elsif ($Opt{'output-format'} eq "svg") {
1010 $Line .= (
1011 ($last_lon == 1000) || $pause_len
1012 ? join("",
1013 "$svg_start_thing<path\n",
1014 " stroke=\"blue\"\n",
1015 " stroke-width=\"0.001\"\n",
1016 " fill=\"none\"\n",
1017 " d=\"\n",
1018 "M $Dat{'lon'} $Dat{'lat'}\n")
1019 : "L $Dat{'lon'} $Dat{'lat'}\n"
1021 } elsif ($Opt{'output-format'} eq "ygraph") {
1022 if (!length($Dat{'error'})) {
1023 my $Time = $print_time ? ($ep_time - $first_time) * 1 : 0;
1024 $Line .= "\"Time = $Time.0\n$Dat{'lon'} $Dat{'lat'}\n\n";
1026 } elsif ($Opt{'output-format'} eq "csv") {
1027 # {{{
1028 if (!length($Dat{'error'})) {
1029 $Dat{'format'} = "csv";
1030 $Line .= join("\t",
1031 $print_time
1032 ? $Opt{'epoch'}
1033 ? $ep_time
1034 : $Opt{'short-date'}
1035 ? "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
1036 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z"
1037 : "$Dat{'year'}-$Dat{'month'}-$Dat{'day'}T" .
1038 "$Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}Z"
1039 : "",
1040 $Dat{'lon'},
1041 $Dat{'lat'},
1042 $print_ele ? $Dat{'ele'} : "", # Elevation
1043 "\n"
1046 # }}}
1047 } elsif ($Opt{'output-format'} eq "pgwtab") {
1048 # FIXME: NOP at the moment.
1049 } else {
1050 die("$progname: \"$Opt{'output-format'}\": " .
1051 "Unknown output format\n");
1053 # }}}
1056 if (!$last_time && $Opt{'output-format'} eq "ps") {
1057 $Line .= "$Dat{'lon'} $Dat{'lat'} m\n";
1060 if ($do_print) {
1061 if ($found_move) {
1062 if ($Opt{'output-format'} eq "gpsml") {
1063 $Line = "<break/>\n$Line";
1065 (!$pause_len && ($Opt{'output-format'} eq "xgraph"))
1066 && ($Line .= "move $Line");
1067 ($Opt{'output-format'} eq "clean") && ($Line .= "\n");
1068 if ($Opt{'output-format'} eq "gpx") {
1069 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n" .
1070 "$Spc$Spc$Spc$Spc<trkseg>\n";
1072 $found_move = 0;
1074 print($Line);
1076 $print_time && ($last_time = $ep_time);
1077 if ($print_pos) {
1078 $last_lon = $Dat{'lon'};
1079 $last_lat = $Dat{'lat'};
1081 $last_line = $data_line;
1082 $svg_start_thing = "\"/>\n";
1083 # }}}
1086 sub ps_header {
1087 # Send a Postscript header to stdout {{{
1088 my ($bl_lon, $bl_lat, $br_lon, $br_lat) = @_;
1089 my $Date = sec_to_string(time);
1090 return(join("",
1091 "%!PS-Adobe-3.0 EPSF-3.0\n",
1092 "%%Creator: $rcs_id\n",
1093 "%%Title:\n",
1094 "%%CreationDate: $Date\n",
1095 "%%BoundingBox: $bl_lon $bl_lat $br_lon $br_lat\n",
1096 "%%DocumentData: Clean7Bit\n",
1097 "%%EndComments\n",
1098 "%%BeginProlog\n",
1099 "/bd { bind def } bind def\n",
1100 "/incompound false def\n",
1101 "/m { moveto } bd\n",
1102 "/l { lineto } bd\n",
1103 "/c { curveto } bd\n",
1104 "/F { incompound not {fill} if } bd\n",
1105 "/f { closepath F } bd\n",
1106 "/S { stroke } bd\n",
1107 "/*u { /incompound true def } bd\n",
1108 "/*U { /incompound false def f} bd\n",
1109 "/k { setcmykcolor } bd\n",
1110 "/K { k } bd\n",
1111 "%%EndProlog\n",
1112 "%%BeginSetup\n",
1113 "%%EndSetup\n",
1115 # }}}
1118 sub print_version {
1119 # Print program version {{{
1120 for (@main::version_array) {
1121 print("$_\n");
1123 # }}}
1124 } # print_version()
1126 sub usage {
1127 # Send the help message to stdout {{{
1128 my $Retval = shift;
1130 if ($Opt{'verbose'}) {
1131 print("\n");
1132 print_version();
1134 print(<<END);
1136 Converts between various GPS formats.
1138 Usage: $progname [options] [file [files [...]]]
1139 $progname -S [file [files [...]]]
1140 $progname -u [file [files [...]]]
1142 Options:
1144 --chronology
1145 Check for broken chronology, warn about entries with an old
1146 timestamp.
1147 -d, --skip-dups
1148 Skip duplicated coordinates.
1149 -e, --epoch
1150 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1151 --fix
1152 Comment out entries which is obviously wrong. Use together with
1153 --chronology to fix those kind of errors. Does not work with GPX
1154 output yet.
1155 --from-date x
1156 Used by the pgwupd format. Specifies from which date waypoints
1157 should be updated. No checks for valid date format here, let
1158 PostgreSQL take care of that. All variants it understands can be
1159 used here.
1160 -h, --help
1161 Show this help.
1162 --inside
1163 Print only trackpoints inside a rectangle specified by --pos1 and
1164 --pos2.
1165 -n, --undefined x
1166 Use x as undefined value. Default: "$Udef".
1167 --near
1168 Add names of the three closest waypoints to the trackpoint.
1169 Unfinished and experimental, needs gpsbabel, which is called from
1170 the program as "$Cmd{'gpsbabel'}".
1171 -o, --output-format x
1172 Use output format x:
1173 clean
1175 gpsml (Default)
1176 gpstrans
1177 gpx (Not complete)
1178 pgtab
1179 pgwtab
1180 pgwupd
1181 poscount
1182 ps (Unfinished)
1183 svg (Unfinished)
1184 xgraph
1185 ygraph
1186 --outside
1187 Print only trackpoints outside a rectangle specified by --pos1 and
1188 --pos2.
1189 --pos1 x
1190 --pos2 x
1191 Specifies one corner where x is in "lat,lon" format (decimal
1192 degrees, negative for west or south) of area rectangle used by the
1193 --inside and --outside options.
1194 -r, --require x
1195 Specify requirements for trackpoints to be written. x is a string
1196 with the following flags:
1198 Print only waypoints which have an elevation.
1200 Print only waypoints which have a position.
1202 Print only waypoints which have a timestamp.
1203 -R, --round x=y[,x2=y2[...]]
1204 Round trackpoint element x to y decimals. Example:
1205 --round lat=4,lon=5,ele=1
1206 -s, --short-date
1207 Use short date format.
1208 -S, --save-to-file x
1209 Save the unconverted data to a file with a filename starting with
1210 the timestamp of the first trackpoint. The parameter string x is
1211 added at the end of the filename. For the time being this option
1212 will ignore all other options. Note: If several files are specified
1213 on the command line, all data will be saved into only one file. This
1214 behaviour may change in the future.
1215 -t, --create-breaks
1216 Create breaks in track between points with a difference more than
1217 $PAUSE_LIMIT seconds.
1218 -T x, --time-shift x
1219 Move time of trackpoint x seconds forwards or backwards. x can be a
1220 positive or negative integer.
1221 -v, --verbose
1222 Increase level of verbosity. Can be repeated.
1223 --version
1224 Print version information.
1225 -w, --strip-whitespace
1226 Strip all unnecessary whitespace.
1227 -y, --double-y-scale
1228 Double Y scale (latitude) to get it right in gnuplot.
1229 --debug
1230 Print debugging messages.
1233 exit($Retval);
1234 # }}}
1235 } # usage()
1237 sub msg {
1238 # Print a status message to stderr based on verbosity level {{{
1239 my ($verbose_level, $Txt) = @_;
1241 if ($Opt{'verbose'} >= $verbose_level) {
1242 print(STDERR "$progname: $Txt\n");
1244 # }}}
1245 } # msg()
1247 __END__
1249 # Law talk {{{
1250 # Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1252 # This program is free software; you can redistribute it and/or modify
1253 # it under the terms of the GNU General Public License as published by
1254 # the Free Software Foundation; either version 2 of the License, or (at
1255 # your option) any later version.
1257 # This program is distributed in the hope that it will be useful, but
1258 # WITHOUT ANY WARRANTY; without even the implied warranty of
1259 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1260 # See the GNU General Public License for more details.
1262 # You should have received a copy of the GNU General Public License
1263 # along with this program; if not, write to the Free Software
1264 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
1265 # USA
1266 # }}}
1268 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :
1269 # End of file $Id$