3 #=======================================================================
5 # File ID: 082106ec-f924-11dd-b757-0001805bf4b1
6 # Converts between various GPS formats
9 # ©opyleft 2002– Øyvind A. Holm <sunny@sunbase.org>
10 # License: GNU General Public License version 3 or later, see end of
11 # file for legal stuff.
12 #=======================================================================
17 use Time
::Local qw
{ timegm_nocheck
};
20 push(@INC, "$ENV{'HOME'}/bin/src/gpstools");
34 # Initial values for command line arguments {{{
39 'double-y-scale' => 0,
45 'output-format' => "gpsml",
51 'save-to-file' => "\n", # \n = undefined, it’s banned in filenames anyway.
54 'strip-whitespace' => 0,
64 $progname =~ s/^.*\/(.*?)$/$1/;
65 our $VERSION = "0.00";
67 Getopt
::Long
::Configure
("bundling");
69 # Command line options {{{
71 "chronology" => \
$Opt{'chronology'},
72 "create-breaks|t" => \
$Opt{'create-breaks'},
73 "debug" => \
$Opt{'debug'},
74 "double-y-scale|y" => \
$Opt{'double-y-scale'},
75 "epoch|e" => \
$Opt{'epoch'},
76 "fix" => \
$Opt{'fix'},
77 "from-date=s" => \
$Opt{'from-date'},
78 "help|h" => \
$Opt{'help'},
79 "inside" => \
$Opt{'inside'},
80 "output-format|o=s" => \
$Opt{'output-format'},
81 "outside" => \
$Opt{'outside'},
82 "pos1=s" => \
$Opt{'pos1'},
83 "pos2=s" => \
$Opt{'pos2'},
84 "require|r=s" => \
$Opt{'require'},
85 "round|R=s" => \
$Opt{'round'},
86 "save-to-file|S=s" => \
$Opt{'save-to-file'},
87 "short-date|s" => \
$Opt{'short-date'},
88 "skip-dups|d" => \
$Opt{'skip-dups'},
89 "strip-whitespace|w" => \
$Opt{'strip-whitespace'},
90 "time-shift|T=i" => \
$Opt{'time-shift'},
91 "undefined|n=s" => \
$Opt{'undefined'},
92 "verbose|v+" => \
$Opt{'verbose'},
93 "version" => \
$Opt{'version'},
96 ) || die("$progname: Option error. Use -h for help.\n");
100 my $PAUSE_LIMIT = 2 * 60; # Antall sekunder mellom to punkter det må til før en move legges inn.
102 my $DIGIT = '[0-9\.\-\+]'; # Used in regexps
103 $GPST::Spc
= $Opt{'strip-whitespace'} ?
"" : " ";
104 my $Spc = $GPST::Spc
; # FIXME
105 my $found_move = 0; # Settes til 1 hvis en /^# move$/ blir funnet.
108 my ($last_lon, $last_lat, $last_line) =
109 ( 1000, 1000, ""); # Vi kan jo teoretisk sett være i Greenwich eller på ekvator
110 my ($lat1, $lon1, $lat2, $lon2) =
111 (-1000, -1000, 1000, 1000);
114 'gpsbabel' => '/usr/local/bin/gpsbabel',
119 if ($Opt{'output-format'} =~ /^(gpx|pgtab)$/) {
120 $Opt{'require'} .= "p";
123 'ele' => ($Opt{'require'} =~ /e/) ?
1 : 0,
124 'position' => ($Opt{'require'} =~ /p/) ?
1 : 0,
125 'time' => ($Opt{'require'} =~ /t/) ?
1 : 0,
127 $Opt{'require'} =~ /[^ept]/
128 && die("$0: Unknown flag in --require (-r) value\n");
130 $Opt{'debug'} && ($Debug = 1);
131 $Opt{'help'} && usage
(0);
132 if ($Opt{'version'}) {
137 if ($Opt{'pos1'} =~ /^($DIGIT+),($DIGIT+)$/) {
141 if ($Opt{'pos2'} =~ /^($DIGIT+),($DIGIT+)$/) {
156 if ($Opt{'epoch'} && $Opt{'short-date'}) {
157 die("$progname: Cannot mix the --epoch (-e) and --short-date (-s) options\n");
160 if ($Opt{'inside'} && $Opt{'outside'}) {
161 die("$progname: Cannot mix the --inside and --outside options\n");
164 # To avoid printing out extra "/> at the start of svg output:
165 my $svg_start_thing = "";
169 if (defined($Opt{'round'})) {
170 my $R = $Opt{'round'};
171 $R =~ s/([a-z]+)=(\d+)/($Round{$1}=$2, "")/eg;
174 length($Opt{'undefined'}) && ($Udef = $Opt{'undefined'});
176 $Opt{'save-to-file'} eq "\n" && print_header
(*STDOUT
);
182 my $from_stdin = scalar(@ARGV) ?
0 : 1;
184 $from_stdin && push(@ARGV, "-");
186 for my $curr_file (@ARGV) {
187 # Scan through stdin or specified files and send every GPS entry to
190 print(STDERR
"$progname: Opening \"$curr_file\" for read\n") if $Opt{'verbose'};
191 if (open(my $curr_fp, "<$curr_file")) {
196 'year' => '', 'month' => '', 'day' => '',
197 'hour' => '', 'min' => '', 'sec' => '',
200 'lat' => '', 'lon' => '',
205 'curr_file' => $curr_file,
208 $Opt{'epoch'} && ($Dat{'date-format'} = "epoch");
209 $Opt{'short-date'} && ($Dat{'date-format'} = "short");
211 if ($Opt{'save-to-file'} ne "\n") {
212 push(@first_lines, $_);
214 s/^# error // && ($Dat{'error'} = "error");
215 s/^# ?// && ($Dat{'error'} = "desc");
217 if (m
#^<(e?tp)\b(.*?)>(.*?)</(e?tp)>\s*$#) {
218 # gpsml — The main storage format {{{
219 my ($Elem, $Props, $Data) =
221 my $err_str = ($Props =~ /\berr="(.*?)"/) ?
$1 : "error";
222 $Elem eq "etp" && ($Dat{'error'} = $err_str);
224 $Data =~ m
#<time>(.*?)</time># && ($Time = $1);
226 (\d\d\d\d
)-?
(\d\d
)-?
(\d\d
)[T
](\d\d
):?
(\d\d
):?
([\d\
.]+?
)Z
228 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
229 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
234 $Data =~ m
#<lat>($DIGIT*?)</lat># && ($Dat{'lat'} = $1);
235 $Data =~ m
#<lon>($DIGIT*?)</lon># && ($Dat{'lon'} = $1);
236 $Data =~ m
#<ele>($DIGIT*?)</ele># && ($Dat{'ele'} = $1);
237 $Data =~ m
#<desc>(.*?)</desc># && ($Dat{'desc'} = $1);
240 } elsif (m
#^<break\b.*?/>#) {
242 } elsif (m
#^<(title|pause)\b.*?>(.*?)</(title|pause)>#) {
246 } elsif (m
#^<desc\b.*?>(.*$)#s) {
247 $Dat{'what'} = "desc";
249 until ($Txt =~ m
#</desc>#s) {
252 $Txt =~ s
#^(.*)(</desc>.*$)#$1#s;
257 $xml_data .= join("", <$curr_fp>);
258 if (!length($Opt{'output-format'})) {
259 $Opt{'output-format'} = "gpx";
260 print_header
(*STDOUT
);
262 read_xmlfile
($xml_data);
266 } elsif (m
#^(\d+)\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
267 # CSV format, epoch style {{{
268 my ($ep_time, $lon_val, $lat_val, $Alt) =
270 $Dat{'epoch'} = $ep_time;
271 ($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
272 $Dat{'day'}, $Dat{'month'}, $Dat{'year'},
273 $Dat{'wday'}, $Dat{'yday'}) = gmtime($ep_time);
274 $Dat{'month'}++; # Urgh Ⅰ
275 $Dat{'year'} += 1900; # Urgh Ⅱ
280 (\d\d\d\d
)-?
(\d\d
)-?
(\d\d
)[T\
](\d\d
):?
(\d\d
):?
(\d\d
)Z?
\t
281 ($DIGIT+)\t($DIGIT+)\t($DIGIT)
284 # CSV format, human-readable date format {{{
285 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
286 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
287 $Dat{'lon'}, $Dat{'lat'}, $Dat{'ele'}) =
293 } elsif (/^Trackpoint\t/) {
294 # Trackpoint\tN60.41630 E5.31675\t09.02.2006 20:24:37 (UTC)\t13.6 m\t\t93.9 m\t00:00:06\t56 kph\t123° true {{{
297 # N60.41630 E5.31675\t
298 # 09.02.2006 20:24:37 (UTC)\t
306 $Orig =~ s/[\r\n]+$//;
307 my ($Marker_f, $Position_f, $Time_f, $Alt_f, $Depth_f,
308 $Leglength_f, $Legtime_f, $Legspeed_f, $Legcourse_f) =
310 # Nødløsning for å unngå at variabler
312 "\t\t\t\t\t\t\t\t\t\t"
315 # "Position_f=\"$Position_f\" \x7B\x7B\x7B\n",
316 # "Time_f=\"$Time_f\"\n",
317 # "Alt_f=\"$Alt_f\"\n",
318 # "Depth_f=\"$Depth_f\"\n",
319 # "Leglength_f=\"$Leglength_f\"\n",
320 # "Legtime_f=\"$Legtime_f\"\n",
321 # "Legspeed_f=\"$Legspeed_f\"\n",
322 # "Legcourse_f=\"$Legcourse_f\" \x7D\x7D\x7D\n",
327 $Legtime_hour, $Legtime_min, $Legtime_sec,
328 $Legspeed, $Legspeed_unit,
330 ) = ("", "", "", "", "", "", "", "", "", "", "", "", "",
331 "", "", "", "", "", "", "", "", "", "");
332 ($Position_f =~ /^(N|S)([\d\.]+) (W|E)([\d\.]+)/) &&
333 ($NS = $1, $Dat{'lat'} = $2, $WE = $3, $Dat{'lon'} = $4);
334 ($Time_f =~ /^(\d+)\.(\d+)\.(\d+) (\d+):(\d+):(\d+) \((.+?)\)/) &&
335 ($Dat{'day'} = $1, $Dat{'month'} = $2, $Dat{'year'} = $3,
336 $Dat{'hour'} = $4, $Dat{'min'} = $5, $Dat{'sec'} = $6);
337 ($Alt_f =~ /^($DIGIT+) (.*?)/) &&
338 ($Dat{'ele'} = $1, $Alt_unit = $2);
339 # D("ele = \"$Dat{'ele'}\"");
340 ($NS eq "S") && ($Dat{'lat'} = 0-$Dat{'lat'});
341 ($WE eq "W") && ($Dat{'lon'} = 0-$Dat{'lon'});
342 # MapSource in win xp writes YYYY, but YY in win98se.
344 defined($Dat{'year'})
345 && $Dat{'year'} =~ /\d/
346 && $Dat{'year'} < 1900
347 ) && ($Dat{'year'} += 2000);
350 } elsif (/^Track\t(.*?)\t/) {
351 $Dat{'title'} = txt_to_xml
($1);
352 $Dat{'what'} = "title";
358 (\d\d
)/(\d\d)/(\d\d\d\d
)\
(\d\d
):(\d\d
):(\d\d
)\t
363 # T 09/01/2002 11:51:26 60°23'36.3" 5°
19'35.9" {{{
364 my ($lat_d, $lat_m, $lat_s, $lon_d, $lon_m, $lon_s);
365 ($Dat{'month
'}, $Dat{'day
'}, $Dat{'year
'},
366 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'},
367 $lat_d, $lat_m, $lat_s,
368 $lon_d, $lon_m, $lon_s) =
373 my $Flat = defined($Round{'lat
'}) ? ".$Round{'lat
'}" : "";
374 my $Flon = defined($Round{'lon
'}) ? ".$Round{'lon
'}" : "";
375 $Dat{'lat
'} = sprintf("%${Flat}f",
376 1.0*($lat_d+($lat_m/60)+($lat_s/3600)));
377 $Dat{'lon
'} = sprintf("%${Flon}f",
378 1.0*$lon_d+($lon_m/60)+($lon_s/3600));
383 1\ (\S+)\ (\S+)\ (\S+)\ (\S+)\x20
384 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)
387 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
388 ($Dat{'lat
'}, $Dat{'lon
'}, $Dat{'speed
'},
390 $Dat{'month
'}, $Dat{'day
'}, $Dat{'year
'},
391 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}) =
399 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
409 (\d\d) # Latitude degree
410 (\d\d) # Latitude minute
411 (\d\d\d) # Latitude minute decimals
413 (\d\d\d) # Longitude degree
414 (\d\d) # Longitude minute
415 (\d\d\d) # Longitude minute degree
421 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
422 $lon_degmin, $lon_mindec);
423 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'}, $Dat{'hour
'},
424 $Dat{'min
'}, $Dat{'sec
'}, $NS, $lat_deg,
425 $lat_degmin, $lat_mindec, $EW,
426 $lon_deg, $lon_degmin, $lon_mindec,
427 $Dat{'accur
'}, $Dat{'ele
'}, $Dat{'unknown
'}) =
428 ($2+2000, $3, $4, $5,
433 my $ep_time = timegm_nocheck(
434 $Dat{'sec
'}, $Dat{'min
'}, $Dat{'hour
'},
435 $Dat{'day
'}, $Dat{'month
'}-1, $Dat{'year
'}
437 $last_time = $ep_time;
438 my $Flat = defined($Round{'lat
'}) ? ".$Round{'lat
'}" : "";
439 my $Flon = defined($Round{'lon
'}) ? ".$Round{'lon
'}" : "";
440 my $tmp_lon = sprintf(
445 my $tmp_lat = sprintf("%${Flat}f",
449 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
450 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
451 $Dat{'lat
'} = $tmp_lat;
452 $Dat{'lon
'} = $tmp_lon;
455 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(_{42})/) {
456 # @020721221336__________________________________________ {{{
457 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'},
458 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}, $Dat{'rest
'}) =
461 $Dat{'error
'} = "nosignal";
464 } elsif (/^xmaplog /) {
467 ($Opt{'output
-format
'} eq "csv")
468 && ($Opt{'save
-to
-file
'} eq "\n")
470 } elsif (/^Pause: /) {
471 # NOP, is here to cope with old files I’ve lying around.
472 } elsif ($Dat{'error
'} eq "desc") {
474 if (defined($Comment)) {
475 $Comment =~ s/^\s*(.*?)\s*$/$1/;
476 if ($Opt{'output
-format
'} eq "gpsml") {
477 $Dat{'desc
'} = txt_to_xml($Comment);
478 $Dat{'what
'} = "desc";
483 $Opt{'verbose
'} && warn("Line $.: Unknown: \"$_\"\n");
488 warn("$progname: $curr_file: Cannot open file for read: $!\n");
493 print_footer(*STDOUT);
499 my $Txt = join("", @_);
500 $Txt =~ s/<!--(.*?)-->//gs;
501 $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
509 # D("print_xml_gps(\"$Orig\")\n");
510 $Str =~ s/<!--(.*?)-->//gs;
511 my $fromdate_str = "";
512 if ($Opt{'from
-date
'}) {
513 $fromdate_str = "date >= '$Opt{'from-date'}' AND ";
515 if ($Opt{'output
-format
'} =~ /^(pgwtab|pgwupd)$/) {
519 <wpt\b(.*?)>(.*?)</wpt>
524 my ($Lat, $Lon, $Name, $Ele, $Type, $Time, $Cmt, $Desc, $Src, $Sym) =
525 ('\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
');
527 $attr_wpt =~ /.*lat="($DIGIT+?)"/s &&
528 ($Lat = postgresql_copy_safe($1));
529 $attr_wpt =~ /.*lon="($DIGIT+?)"/s &&
530 ($Lon = postgresql_copy_safe($1));
531 $el_wpt =~ /.*<name\b(.*?)>(.*?)<\/name>/s &&
532 ($Name = postgresql_copy_safe(xml_to_txt($2)));
533 $el_wpt =~ /.*<ele\b(.*?)>(.*?)<\/ele>/s &&
534 ($Ele = postgresql_copy_safe(xml_to_txt($2)));
535 $el_wpt =~ /.*<type\b(.*?)>(.*?)<\/type>/s &&
536 ($Type = postgresql_copy_safe(xml_to_txt($2)));
537 $el_wpt =~ /.*<time\b(.*?)>(.*?)<\/time>/s &&
538 ($Time = postgresql_copy_safe(xml_to_txt($2)));
539 $el_wpt =~ /.*<cmt\b(.*?)>(.*?)<\/cmt>/s &&
540 ($Cmt = postgresql_copy_safe(xml_to_txt($2)));
541 $el_wpt =~ /.*<desc\b(.*?)>(.*?)<\/desc>/s &&
542 ($Desc = postgresql_copy_safe(xml_to_txt($2)));
543 $el_wpt =~ /.*<src\b(.*?)>(.*?)<\/src>/s &&
544 ($Src = postgresql_copy_safe(xml_to_txt($2)));
545 $el_wpt =~ /.*<sym\b(.*?)>(.*?)<\/sym>/s &&
546 ($Sym = postgresql_copy_safe(xml_to_txt($2)));
548 if (length($Opt{'round
'})) {
549 if (defined($Round{'lat
'}) && length($Lat)) {
550 ($Lat = 1.0 * sprintf("%.$Round{'lat
'}f", $Lat));
552 if (defined($Round{'lon
'}) && length($Lon)) {
553 ($Lon = 1.0 * sprintf("%.$Round{'lon
'}f", $Lon));
555 if (defined($Round{'ele
'}) && $Ele ne '\N
') {
556 ($Ele = 1.0 * sprintf("%.$Round{'ele
'}f", $Ele));
560 if ($Opt{'output
-format
'} eq "pgwtab") {
574 } elsif ($Opt{'output
-format
'} eq "pgwupd") {
578 "$Spc${Spc}UPDATE logg SET name = clname(coor) " .
579 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
580 "$Spc${Spc}UPDATE logg SET dist = cldist(coor) " .
581 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
592 <trk\b(.*?)>(.*?)</trk>
598 <name\b(.*?)>(.*?)</name>
601 $tmp_dat{'title
'} = $2;
602 $tmp_dat{'what
'} = "title";
603 $tmp_dat{'error
'} = "";
604 print_entry(%tmp_dat);
609 <trkseg\b(.*?)>(.*?)</trkseg>
615 <trkpt\b(.*?)>(.*?)</trkpt>
618 my ($attr_trkpt, $el_trkpt) =
621 'year
' => '', 'month
' => '', 'day
' => '',
622 'hour
' => '', 'min
' => '', 'sec
' => '',
625 'lat
' => '', 'lon
' => '',
631 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon
'} = $1);
632 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat
'} = $1);
633 ($el_trkpt =~ m#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele
'} = $1);
637 <time>(\d\d\d\d)-?(\d\d)-?(\d\d)T
638 (\d\d):?(\d\d):?([\d\.]+)Z</time>
641 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'},
642 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}) =
643 ($1, $2, $3, $4, $5, $6);
660 if ($Opt{'output
-format
'} eq "gpsml") {
661 print($out_fp join("",
662 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
666 } elsif ($Opt{'output
-format
'} eq "gpstrans") {
667 print($out_fp "Format: DMS UTC Offset: 0.00 hrs " .
668 "Datum[100]: WGS 84\n");
669 } elsif ($Opt{'output
-format
'} eq "gpx") {
670 print($out_fp join("",
671 qq{<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n},
673 qq{$Spc${Spc}version="1.1"\n},
674 qq{$Spc${Spc}creator="gpst - http://sunny256.github.com/gpstools/"\n},
675 qq{$Spc${Spc}xmlns="http://www.topografix.com/GPX/1/1"\n},
676 qq{$Spc${Spc}xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n},
677 qq{$Spc${Spc}xsi:schemaLocation="http://www.topografix.com/GPX/1/1 },
678 qq{http://www.topografix.com/GPX/1/1/gpx.xsd"\n},
681 qq{$Spc$Spc$Spc$Spc<trkseg>\n},
683 } elsif ($Opt{'output
-format
'} eq "ps") {
684 print($out_fp ps_header(532, 6034, 533, 6040));
685 print($out_fp "*u\n");
686 } elsif ($Opt{'output
-format
'} eq "svg") {
687 print($out_fp join("",
688 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
689 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
690 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
691 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
692 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
693 "$Spc$Spc<title></title>\n",
694 "$Spc$Spc<desc></desc>\n",
703 if ($Opt{'output
-format
'} eq "gpsml") {
704 print($out_fp join("",
708 } elsif ($Opt{'output
-format
'} eq "gpx") {
709 print($out_fp join("",
710 "$Spc$Spc$Spc$Spc</trkseg>\n",
714 } elsif ($Opt{'output
-format
'} eq "poscount") {
715 while (my ($l_name, $l_val) = each %Poscount) {
716 $l_name =~ /^(.+?),(.+?)$/
717 && print($out_fp "$1\t$2\t$l_val\n");
719 } elsif ($Opt{'output
-format
'} eq "ps") {
720 print($out_fp join("",
725 } elsif ($Opt{'output
-format
'} eq "svg") {
726 print($out_fp "\"/>\n</svg>\n");
732 # Print a GPS entry with time, latitude, longitude and elevation in
736 defined($Dat{'desc
'}) || ($Dat{'desc
'} = "");
737 defined($Dat{'ele
'}) || ($Dat{'ele
'} = "");
738 defined($Dat{'lat
'}) || ($Dat{'lat
'} = "");
739 defined($Dat{'lon
'}) || ($Dat{'lon
'} = "");
740 defined($Dat{'year
'}) || ($Dat{'year
'} = "");
741 my $print_time = length($Dat{'year
'}) ? 1 : 0;
743 if (!$Req{'position
'} && $Opt{'output
-format
'} eq "gpsml") {
744 $print_pos = (length($Dat{'lat
'}) || length($Dat{'lon
'})) ? 1 : 0;
746 $print_pos = (length($Dat{'lat
'}) && length($Dat{'lon
'})) ? 1 : 0;
749 $Dat{'lat
'} = $Dat{'lon
'} = "";
751 my $print_ele = length($Dat{'ele
'}) ? 1 : 0;
752 my $print_desc = length($Dat{'desc
'}) ? 1 : 0;
754 # D("print_entry(\"" . join("\", \"", @_) . "\");");
757 if (length($Opt{'round
'})) {
758 for my $Tmp (qw{ lat lon ele }) {
759 if (defined($Round{$Tmp}) && length($Dat{$Tmp})) {
761 ($Dat{$Tmp} = num_expand(1.0 * sprintf("%.$Round{$Tmp}f", $Dat{$Tmp})));
766 if ($Opt{'output
-format
'} eq "poscount") {
767 # FIXME: Sort output in some way
768 if (!length($Dat{'error
'})) {
769 $Dat{'lat
'} = num_expand($Dat{'lat
'});
770 $Dat{'lon
'} = num_expand($Dat{'lon
'});
771 my $Name = "$Dat{'lon
'},$Dat{'lat
'}";
772 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
779 $ep_time = timegm_nocheck(
780 $Dat{'sec
'}, $Dat{'min
'}, $Dat{'hour
'},
781 $Dat{'day
'}, $Dat{'month
'} - 1, $Dat{'year
'}
783 if ($Opt{'time-shift'}) {
784 # D("ep_time før: '$ep_time'");
785 $ep_time += $Opt{'time-shift'};
786 # D("ep_time etter: '$ep_time'");
787 ($Dat{'sec
'}, $Dat{'min
'},$Dat{'hour
'}, $Dat{'day
'},
788 $Dat{'month
'}, $Dat{'year
'}) = gmtime($ep_time);
789 $Dat{'year
'} += 1900;
792 $Dat{'epoch
'} = $ep_time;
793 $Dat{'year
'} = sprintf("%04u", $Dat{'year
'});
794 $Dat{'month
'} = sprintf("%02u", $Dat{'month
'});
795 $Dat{'day
'} = sprintf("%02u", $Dat{'day
'});
796 $Dat{'hour
'} = sprintf("%02u", $Dat{'hour
'});
797 $Dat{'min
'} = sprintf("%02u", $Dat{'min
'});
798 $Dat{'sec
'} = sprintf("%02u", $Dat{'sec
'});
799 if ($Opt{'chronology
'}) {
800 if ($last_time > $ep_time && !length($Dat{'error
'})) {
802 "%s: $Dat{'curr_file
'}: \"%sZ\": Next date is %s in the past (%sZ)\n",
803 $progname, sec_to_string($last_time, "T"),
804 sec_to_readable($last_time-$ep_time),
805 sec_to_string($ep_time, "T")
807 # FIXME: Make --fix work with gpx.
808 if ($Opt{'fix
'} && ($Opt{'output
-format
'} !~ /^gpx$/)) {
809 $Dat{'error
'} = "chrono";
811 } elsif ($last_time == $ep_time && !length($Dat{'error
'})) {
813 "%s: $Dat{'curr_file
'}: \"%sZ\": Duplicated time\n",
814 $progname, sec_to_string($last_time, "T")
816 # FIXME: Make --fix work with gpx.
817 if ($Opt{'fix
'} && ($Opt{'output
-format
'} !~ /^gpx$/)) {
818 $Dat{'error
'} = "duptime";
832 if ($Opt{'save
-to
-file
'} ne "\n") {
834 $print_time || return;
835 my $base_name = "$Dat{'year
'}$Dat{'month
'}$Dat{'day
'}T" .
836 "$Dat{'hour
'}$Dat{'min
'}$Dat{'sec
'}Z" .
837 "$Opt{'save
-to
-file
'}";
838 my $file_name = $base_name;
840 for (my $a = 1; (-e $file_name) && ($a < 1000); $a++) {
841 $file_name = "$base_name.dup_$a";
844 die("$progname: $base_name: File already exists, and ran " .
845 "out of attempts to create unique file name\n");
847 if ($Opt{'verbose
'}) {
848 warn("$progname: $base_name: File already exists, using " .
849 "unique name \"$file_name\" instead\n");
852 if (open(my $to_fp, ">", $file_name)) {
853 print_header(*$to_fp);
861 ) || die("$progname: $file_name: Cannot write to file: $!\n");
862 print_footer(*$to_fp);
864 if ($Opt{'output
-format
'} eq "gpsml") {
865 printf("<include>%s</include>\n",
866 txt_to_xml($file_name));
867 } elsif ($Opt{'output
-format
'} eq "gpx") {
868 printf("<!-- Saved unconverted data to \"%s\" -->\n",
869 txt_to_xml($file_name));
871 print("$progname: Saved unconverted data to \"$file_name\"\n");
875 die("$progname: $file_name: Cannot create file: $!\n");
883 if ($Dat{'what
'} eq "tp") {
885 if ($Opt{'require'}) {
886 $Req{'time'} && !$print_time && return;
887 $Req{'position
'} && !$print_pos && return;
888 $Req{'ele
'} && !$print_ele && return;
891 if ($Opt{'inside
'} || $Opt{'outside
'}) {
893 ($Dat{'lat
'} < $lat1) ||
894 ($Dat{'lat
'} > $lat2) ||
895 ($Dat{'lon
'} < $lon1) ||
896 ($Dat{'lon
'} > $lon2)
898 $Opt{'inside
'} && return;
900 $Opt{'outside
'} && return;
904 if ($Opt{'output
-format
'} eq "ps") {
911 && ($Dat{'lon
'} eq $last_lon)
912 && ($Dat{'lat
'} eq $last_lat)
914 if ($Opt{'output
-format
'} eq 'gpsml
') {
915 $Dat{'error
'} = "dup";
924 $Opt{'create
-breaks
'}
925 && $ep_time-$last_time > $PAUSE_LIMIT
928 $pause_len = $ep_time-$last_time;
929 # D("pause_len set to '$pause_len'");
932 $Line .= pause_entry($pause_len, $ep_time, $last_time);
937 # Valid data was found, send to stdout {{{
938 unless ($first_time) {
939 $first_time = $ep_time;
941 $Line .= gen_entry($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat);
945 if (!$last_time && $Opt{'output
-format
'} eq "ps") {
946 $Line .= "$Dat{'lon
'} $Dat{'lat
'} m\n";
951 if ($Opt{'output
-format
'} eq "gpsml") {
952 $Line = "<break/>\n$Line";
954 (!$pause_len && ($Opt{'output
-format
'} eq "xgraph"))
955 && ($Line .= "move $Line");
956 ($Opt{'output
-format
'} eq "clean") && ($Line .= "\n");
957 if ($Opt{'output
-format
'} eq "gpx") {
958 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n" .
959 "$Spc$Spc$Spc$Spc<trkseg>\n";
965 $print_time && ($last_time = $ep_time);
967 $last_lon = $Dat{'lon
'};
968 $last_lat = $Dat{'lat
'};
970 $last_line = $data_line;
971 $svg_start_thing = "\"/>\n";
976 # Generate trackpoint entry, calls trackpoint() {{{
977 my ($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat) = @_;
979 if ($Opt{'double
-y
-scale
'} && length($Dat{'lat
'})) {
982 if ($Opt{'output
-format
'} eq "gpsml") {
983 if ($Dat{'what
'} eq "tp") {
984 $Dat{'format
'} = "gpsml";
985 $Line .= trackpoint(%Dat);
986 } elsif ($Dat{'what
'} =~ /^(pause|desc|title)$/) {
987 $Line .= sprintf("<%s>%s</%s>\n",
992 } elsif ($Opt{'output
-format
'} eq "pgtab") {
993 if ($Dat{'what
'} eq "tp" && !length($Dat{'error
'})) {
994 $Dat{'format
'} = "pgtab";
995 $Line .= trackpoint(%Dat);
997 } elsif ($Opt{'output
-format
'} eq "xgraph") {
998 if ($print_pos && !length($Dat{'error
'})) {
999 $Dat{'format
'} = "xgraph";
1000 $Line .= trackpoint(%Dat);
1002 } elsif($Opt{'output
-format
'} eq "gpstrans") {
1003 if ($print_pos && !length($Dat{'error
'})) {
1004 $Dat{'format
'} = "gpstrans";
1005 $Line .= trackpoint(%Dat);
1007 } elsif($Opt{'output
-format
'} eq "gpx") {
1008 if ($Dat{'what
'} eq "tp") {
1009 $Dat{'format
'} = "gpx";
1010 $Line .= trackpoint(%Dat);
1012 } elsif ($Opt{'output
-format
'} eq "clean") {
1013 if ($Dat{'what
'} eq "tp" && !length($Dat{'error
'})) {
1014 $Dat{'format
'} = "clean";
1015 $Line .= trackpoint(%Dat);
1017 } elsif ($Opt{'output
-format
'} eq "ps") {
1020 ? "f\n$Dat{'lon
'} $Dat{'lat
'} m\n"
1021 : "$Dat{'lon
'} $Dat{'lat
'} l\n"
1023 } elsif ($Opt{'output
-format
'} eq "svg") {
1025 ($last_lon == 1000) || $pause_len
1027 "$svg_start_thing<path\n",
1028 " stroke=\"blue\"\n",
1029 " stroke-width=\"0.001\"\n",
1032 "M $Dat{'lon
'} $Dat{'lat
'}\n")
1033 : "L $Dat{'lon
'} $Dat{'lat
'}\n"
1035 } elsif ($Opt{'output
-format
'} eq "ygraph") {
1036 if (!length($Dat{'error
'})) {
1037 $Dat{'lat
'} = num_expand($Dat{'lat
'});
1038 $Dat{'lon
'} = num_expand($Dat{'lon
'});
1039 $Dat{'ele
'} = num_expand($Dat{'ele
'});
1040 my $Time = $print_time ? ($ep_time - $first_time) * 1 : 0;
1041 $Line .= "\"Time = $Time.0\n$Dat{'lon
'} $Dat{'lat
'}\n\n";
1043 } elsif ($Opt{'output
-format
'} eq "csv") {
1045 if (!length($Dat{'error
'})) {
1046 $Dat{'format
'} = "csv";
1047 $Dat{'lat
'} = num_expand($Dat{'lat
'});
1048 $Dat{'lon
'} = num_expand($Dat{'lon
'});
1049 $Dat{'ele
'} = num_expand($Dat{'ele
'});
1054 : $Opt{'short
-date
'}
1055 ? "$Dat{'year
'}$Dat{'month
'}$Dat{'day
'}T" .
1056 "$Dat{'hour
'}$Dat{'min
'}$Dat{'sec
'}Z"
1057 : "$Dat{'year
'}-$Dat{'month
'}-$Dat{'day
'}T" .
1058 "$Dat{'hour
'}:$Dat{'min
'}:$Dat{'sec
'}Z"
1062 $print_ele ? $Dat{'ele
'} : "", # Elevation
1067 } elsif ($Opt{'output
-format
'} eq "pgwtab") {
1068 # FIXME: NOP at the moment.
1070 die("$progname: \"$Opt{'output
-format
'}\": " .
1071 "Unknown output format\n");
1079 my ($pause_len, $ep_time, $last_time) = @_;
1082 if ($Opt{'output
-format
'} eq "gpsml") {
1083 $Line .= sprintf("<pause>%s</pause>\n",
1084 sec_to_readable($ep_time-$last_time));
1085 } elsif ($Opt{'output
-format
'} eq "clean") {
1086 $pause_len && ($Line .= "\n");
1087 } elsif ($Opt{'output
-format
'} eq "csv") {
1088 $Line .= sprintf("# Pause: %s\n# move\n",
1089 sec_to_readable($ep_time-$last_time));
1090 } elsif ($Opt{'output
-format
'} eq "xgraph") {
1091 $pause_len && ($Line .= "move ");
1099 # Send a Postscript header to stdout {{{
1100 my ($bl_lon, $bl_lat, $br_lon, $br_lat) = @_;
1101 my $Date = sec_to_string(time);
1103 "%!PS-Adobe-3.0 EPSF-3.0\n",
1104 "%%Creator: gpst\n",
1106 "%%CreationDate: $Date\n",
1107 "%%BoundingBox: $bl_lon $bl_lat $br_lon $br_lat\n",
1108 "%%DocumentData: Clean7Bit\n",
1111 "/bd { bind def } bind def\n",
1112 "/incompound false def\n",
1113 "/m { moveto } bd\n",
1114 "/l { lineto } bd\n",
1115 "/c { curveto } bd\n",
1116 "/F { incompound not {fill} if } bd\n",
1117 "/f { closepath F } bd\n",
1118 "/S { stroke } bd\n",
1119 "/*u { /incompound true def } bd\n",
1120 "/*U { /incompound false def f} bd\n",
1121 "/k { setcmykcolor } bd\n",
1131 # Print program version {{{
1132 print("$progname v$VERSION\n");
1137 # Send the help message to stdout {{{
1140 if ($Opt{'verbose
'}) {
1146 Converts between various GPS formats.
1148 Usage: $progname [options] [file [files [...]]]
1149 $progname -S [file [files [...]]]
1150 $progname -u [file [files [...]]]
1155 Check for broken chronology, warn about entries with an old
1158 Skip duplicated coordinates.
1160 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1162 Comment out entries which is obviously wrong. Use together with
1163 --chronology to fix those kind of errors. Does not work with GPX
1166 Used by the pgwupd format. Specifies from which date waypoints
1167 should be updated. No checks for valid date format here, let
1168 PostgreSQL take care of that. All variants it understands can be
1173 Print only trackpoints inside a rectangle specified by --pos1 and
1176 Use x as undefined value. Default: "$Udef".
1177 -o, --output-format x
1178 Use output format x:
1193 Print only trackpoints outside a rectangle specified by --pos1 and
1197 Specifies one corner where x is in "lat,lon" format (decimal
1198 degrees, negative for west or south) of area rectangle used by the
1199 --inside and --outside options.
1201 Specify requirements for trackpoints to be written. x is a string
1202 with the following flags:
1204 Print only waypoints which have an elevation.
1206 Print only waypoints which have a position.
1208 Print only waypoints which have a timestamp.
1209 -R, --round x=y[,x2=y2[...]]
1210 Round trackpoint element x to y decimals. Example:
1211 --round lat=4,lon=5,ele=1
1213 Use short date format.
1214 -S, --save-to-file x
1215 Save the unconverted data to a file with a filename starting with
1216 the timestamp of the first trackpoint. The parameter string x is
1217 added at the end of the filename. For the time being this option
1218 will ignore all other options. Note: If several files are specified
1219 on the command line, all data will be saved into only one file. This
1220 behaviour may change in the future.
1222 Create breaks in track between points with a difference more than
1223 $PAUSE_LIMIT seconds.
1224 -T x, --time-shift x
1225 Move time of trackpoint x seconds forwards or backwards. x can be a
1226 positive or negative integer.
1228 Increase level of verbosity. Can be repeated.
1230 Print version information.
1231 -w, --strip-whitespace
1232 Strip all unnecessary whitespace.
1233 -y, --double-y-scale
1234 Double Y scale (latitude) to get it right in gnuplot.
1236 Print debugging messages.
1244 # Print a status message to stderr based on verbosity level {{{
1245 my ($verbose_level, $Txt) = @_;
1247 if ($Opt{'verbose'} >= $verbose_level) {
1248 print(STDERR
"$progname: $Txt\n");
1256 # Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1258 This program is free software
: you can redistribute it
and/or modify it
1259 under the terms of the GNU General Public License as published by the
1260 Free Software Foundation
, either version
3 of the License
, or (at your
1261 option
) any later version
.
1263 This program is distributed
in the hope that it will be useful
, but
1264 WITHOUT ANY WARRANTY
; without even the implied warranty of
1265 MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE
.
1266 See the GNU General Public License
for more details
.
1268 You should have received a copy of the GNU General Public License along
1270 If
not, see L
<http
://www
.gnu
.org
/licenses/>.
1273 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :