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