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