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 $Dat{'lat
'} = 1.0*($lat_d+($lat_m/60)+($lat_s/3600));
374 $Dat{'lon
'} = 1.0*$lon_d+($lon_m/60)+($lon_s/3600);
379 1\ (\S+)\ (\S+)\ (\S+)\ (\S+)\x20
380 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)
383 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
384 ($Dat{'lat
'}, $Dat{'lon
'}, $Dat{'speed
'},
386 $Dat{'month
'}, $Dat{'day
'}, $Dat{'year
'},
387 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}) =
395 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
405 (\d\d) # Latitude degree
406 (\d\d) # Latitude minute
407 (\d\d\d) # Latitude minute decimals
409 (\d\d\d) # Longitude degree
410 (\d\d) # Longitude minute
411 (\d\d\d) # Longitude minute degree
417 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
418 $lon_degmin, $lon_mindec);
419 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'}, $Dat{'hour
'},
420 $Dat{'min
'}, $Dat{'sec
'}, $NS, $lat_deg,
421 $lat_degmin, $lat_mindec, $EW,
422 $lon_deg, $lon_degmin, $lon_mindec,
423 $Dat{'accur
'}, $Dat{'ele
'}, $Dat{'unknown
'}) =
424 ($2+2000, $3, $4, $5,
429 my $ep_time = timegm_nocheck(
430 $Dat{'sec
'}, $Dat{'min
'}, $Dat{'hour
'},
431 $Dat{'day
'}, $Dat{'month
'}-1, $Dat{'year
'}
433 $last_time = $ep_time;
434 my $tmp_lon = $lon_deg + $lon_degmin/60 + $lon_mindec/60000;
435 my $tmp_lat = $lat_deg + $lat_degmin/60 + $lat_mindec/60000;
436 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
437 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
438 $Dat{'lat
'} = $tmp_lat;
439 $Dat{'lon
'} = $tmp_lon;
442 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(_{42})/) {
443 # @020721221336__________________________________________ {{{
444 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'},
445 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}, $Dat{'rest
'}) =
448 $Dat{'error
'} = "nosignal";
451 } elsif (/^xmaplog /) {
454 ($Opt{'output
-format
'} eq "csv")
455 && ($Opt{'save
-to
-file
'} eq "\n")
457 } elsif (/^Pause: /) {
458 # NOP, is here to cope with old files I’ve lying around.
459 } elsif ($Dat{'error
'} eq "desc") {
461 if (defined($Comment)) {
462 $Comment =~ s/^\s*(.*?)\s*$/$1/;
463 if ($Opt{'output
-format
'} eq "gpsml") {
464 $Dat{'desc
'} = txt_to_xml($Comment);
465 $Dat{'what
'} = "desc";
470 $Opt{'verbose
'} && warn("Line $.: Unknown: \"$_\"\n");
475 warn("$progname: $curr_file: Cannot open file for read: $!\n");
480 print_footer(*STDOUT);
486 my $Txt = join("", @_);
487 $Txt =~ s/<!--(.*?)-->//gs;
488 $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
496 # D("print_xml_gps(\"$Orig\")\n");
497 $Str =~ s/<!--(.*?)-->//gs;
498 my $fromdate_str = "";
499 if ($Opt{'from
-date
'}) {
500 $fromdate_str = "date >= '$Opt{'from-date'}' AND ";
502 if ($Opt{'output
-format
'} =~ /^(pgwtab|pgwupd)$/) {
506 <wpt\b(.*?)>(.*?)</wpt>
511 my ($Lat, $Lon, $Name, $Ele, $Type, $Time, $Cmt, $Desc, $Src, $Sym) =
512 ('\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
');
514 $attr_wpt =~ /.*lat="($DIGIT+?)"/s &&
515 ($Lat = postgresql_copy_safe($1));
516 $attr_wpt =~ /.*lon="($DIGIT+?)"/s &&
517 ($Lon = postgresql_copy_safe($1));
518 $el_wpt =~ /.*<name\b(.*?)>(.*?)<\/name>/s &&
519 ($Name = postgresql_copy_safe(xml_to_txt($2)));
520 $el_wpt =~ /.*<ele\b(.*?)>(.*?)<\/ele>/s &&
521 ($Ele = postgresql_copy_safe(xml_to_txt($2)));
522 $el_wpt =~ /.*<type\b(.*?)>(.*?)<\/type>/s &&
523 ($Type = postgresql_copy_safe(xml_to_txt($2)));
524 $el_wpt =~ /.*<time\b(.*?)>(.*?)<\/time>/s &&
525 ($Time = postgresql_copy_safe(xml_to_txt($2)));
526 $el_wpt =~ /.*<cmt\b(.*?)>(.*?)<\/cmt>/s &&
527 ($Cmt = postgresql_copy_safe(xml_to_txt($2)));
528 $el_wpt =~ /.*<desc\b(.*?)>(.*?)<\/desc>/s &&
529 ($Desc = postgresql_copy_safe(xml_to_txt($2)));
530 $el_wpt =~ /.*<src\b(.*?)>(.*?)<\/src>/s &&
531 ($Src = postgresql_copy_safe(xml_to_txt($2)));
532 $el_wpt =~ /.*<sym\b(.*?)>(.*?)<\/sym>/s &&
533 ($Sym = postgresql_copy_safe(xml_to_txt($2)));
535 if (length($Opt{'round
'})) {
536 if (defined($Round{'lat
'}) && length($Lat)) {
537 ($Lat = 1.0 * sprintf("%.$Round{'lat
'}f", $Lat));
539 if (defined($Round{'lon
'}) && length($Lon)) {
540 ($Lon = 1.0 * sprintf("%.$Round{'lon
'}f", $Lon));
542 if (defined($Round{'ele
'}) && $Ele ne '\N
') {
543 ($Ele = 1.0 * sprintf("%.$Round{'ele
'}f", $Ele));
547 if ($Opt{'output
-format
'} eq "pgwtab") {
561 } elsif ($Opt{'output
-format
'} eq "pgwupd") {
565 "$Spc${Spc}UPDATE logg SET name = clname(coor) " .
566 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
567 "$Spc${Spc}UPDATE logg SET dist = cldist(coor) " .
568 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
579 <trk\b(.*?)>(.*?)</trk>
585 <name\b(.*?)>(.*?)</name>
588 $tmp_dat{'title
'} = $2;
589 $tmp_dat{'what
'} = "title";
590 $tmp_dat{'error
'} = "";
591 print_entry(%tmp_dat);
596 <trkseg\b(.*?)>(.*?)</trkseg>
602 <trkpt\b(.*?)>(.*?)</trkpt>
605 my ($attr_trkpt, $el_trkpt) =
608 'year
' => '', 'month
' => '', 'day
' => '',
609 'hour
' => '', 'min
' => '', 'sec
' => '',
612 'lat
' => '', 'lon
' => '',
618 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon
'} = $1);
619 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat
'} = $1);
620 ($el_trkpt =~ m#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele
'} = $1);
624 <time>(\d\d\d\d)-?(\d\d)-?(\d\d)T
625 (\d\d):?(\d\d):?([\d\.]+)Z</time>
628 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'},
629 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}) =
630 ($1, $2, $3, $4, $5, $6);
647 if ($Opt{'output
-format
'} eq "gpsml") {
648 print($out_fp join("",
649 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
653 } elsif ($Opt{'output
-format
'} eq "gpstrans") {
654 print($out_fp "Format: DMS UTC Offset: 0.00 hrs " .
655 "Datum[100]: WGS 84\n");
656 } elsif ($Opt{'output
-format
'} eq "gpx") {
657 print($out_fp join("",
658 qq{<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n},
660 qq{$Spc${Spc}version="1.1"\n},
661 qq{$Spc${Spc}creator="gpst - http://sunny256.github.com/gpstools/"\n},
662 qq{$Spc${Spc}xmlns="http://www.topografix.com/GPX/1/1"\n},
663 qq{$Spc${Spc}xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n},
664 qq{$Spc${Spc}xsi:schemaLocation="http://www.topografix.com/GPX/1/1 },
665 qq{http://www.topografix.com/GPX/1/1/gpx.xsd"\n},
668 qq{$Spc$Spc$Spc$Spc<trkseg>\n},
670 } elsif ($Opt{'output
-format
'} eq "ps") {
671 print($out_fp ps_header(532, 6034, 533, 6040));
672 print($out_fp "*u\n");
673 } elsif ($Opt{'output
-format
'} eq "svg") {
674 print($out_fp join("",
675 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
676 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
677 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
678 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
679 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
680 "$Spc$Spc<title></title>\n",
681 "$Spc$Spc<desc></desc>\n",
690 if ($Opt{'output
-format
'} eq "gpsml") {
691 print($out_fp join("",
695 } elsif ($Opt{'output
-format
'} eq "gpx") {
696 print($out_fp join("",
697 "$Spc$Spc$Spc$Spc</trkseg>\n",
701 } elsif ($Opt{'output
-format
'} eq "poscount") {
702 while (my ($l_name, $l_val) = each %Poscount) {
703 $l_name =~ /^(.+?),(.+?)$/
704 && print($out_fp "$1\t$2\t$l_val\n");
706 } elsif ($Opt{'output
-format
'} eq "ps") {
707 print($out_fp join("",
712 } elsif ($Opt{'output
-format
'} eq "svg") {
713 print($out_fp "\"/>\n</svg>\n");
719 # Print a GPS entry with time, latitude, longitude and elevation in
723 defined($Dat{'desc
'}) || ($Dat{'desc
'} = "");
724 defined($Dat{'ele
'}) || ($Dat{'ele
'} = "");
725 defined($Dat{'lat
'}) || ($Dat{'lat
'} = "");
726 defined($Dat{'lon
'}) || ($Dat{'lon
'} = "");
727 defined($Dat{'year
'}) || ($Dat{'year
'} = "");
728 my $print_time = length($Dat{'year
'}) ? 1 : 0;
730 if (!$Req{'position
'} && $Opt{'output
-format
'} eq "gpsml") {
731 $print_pos = (length($Dat{'lat
'}) || length($Dat{'lon
'})) ? 1 : 0;
733 $print_pos = (length($Dat{'lat
'}) && length($Dat{'lon
'})) ? 1 : 0;
736 $Dat{'lat
'} = $Dat{'lon
'} = "";
738 my $print_ele = length($Dat{'ele
'}) ? 1 : 0;
739 my $print_desc = length($Dat{'desc
'}) ? 1 : 0;
741 # D("print_entry(\"" . join("\", \"", @_) . "\");");
744 if (length($Opt{'round
'})) {
745 for my $Tmp (qw{ lat lon ele }) {
746 if (defined($Round{$Tmp}) && length($Dat{$Tmp})) {
748 ($Dat{$Tmp} = num_expand(1.0 * sprintf("%.$Round{$Tmp}f", $Dat{$Tmp})));
753 if ($Opt{'output
-format
'} eq "poscount") {
754 # FIXME: Sort output in some way
755 if (!length($Dat{'error
'})) {
756 $Dat{'lat
'} = num_expand($Dat{'lat
'});
757 $Dat{'lon
'} = num_expand($Dat{'lon
'});
758 my $Name = "$Dat{'lon
'},$Dat{'lat
'}";
759 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
766 $ep_time = timegm_nocheck(
767 $Dat{'sec
'}, $Dat{'min
'}, $Dat{'hour
'},
768 $Dat{'day
'}, $Dat{'month
'} - 1, $Dat{'year
'}
770 if ($Opt{'time-shift'}) {
771 # D("ep_time før: '$ep_time'");
772 $ep_time += $Opt{'time-shift'};
773 # D("ep_time etter: '$ep_time'");
774 ($Dat{'sec
'}, $Dat{'min
'},$Dat{'hour
'}, $Dat{'day
'},
775 $Dat{'month
'}, $Dat{'year
'}) = gmtime($ep_time);
776 $Dat{'year
'} += 1900;
779 $Dat{'epoch
'} = $ep_time;
780 $Dat{'year
'} = sprintf("%04u", $Dat{'year
'});
781 $Dat{'month
'} = sprintf("%02u", $Dat{'month
'});
782 $Dat{'day
'} = sprintf("%02u", $Dat{'day
'});
783 $Dat{'hour
'} = sprintf("%02u", $Dat{'hour
'});
784 $Dat{'min
'} = sprintf("%02u", $Dat{'min
'});
785 $Dat{'sec
'} = sprintf("%02u", $Dat{'sec
'});
786 if ($Opt{'chronology
'}) {
787 if ($last_time > $ep_time && !length($Dat{'error
'})) {
789 "%s: $Dat{'curr_file
'}: \"%sZ\": Next date is %s in the past (%sZ)\n",
790 $progname, sec_to_string($last_time, "T"),
791 sec_to_readable($last_time-$ep_time),
792 sec_to_string($ep_time, "T")
794 # FIXME: Make --fix work with gpx.
795 if ($Opt{'fix
'} && ($Opt{'output
-format
'} !~ /^gpx$/)) {
796 $Dat{'error
'} = "chrono";
798 } elsif ($last_time == $ep_time && !length($Dat{'error
'})) {
800 "%s: $Dat{'curr_file
'}: \"%sZ\": Duplicated time\n",
801 $progname, sec_to_string($last_time, "T")
803 # FIXME: Make --fix work with gpx.
804 if ($Opt{'fix
'} && ($Opt{'output
-format
'} !~ /^gpx$/)) {
805 $Dat{'error
'} = "duptime";
819 if ($Opt{'save
-to
-file
'} ne "\n") {
821 $print_time || return;
822 my $base_name = "$Dat{'year
'}$Dat{'month
'}$Dat{'day
'}T" .
823 "$Dat{'hour
'}$Dat{'min
'}$Dat{'sec
'}Z" .
824 "$Opt{'save
-to
-file
'}";
825 my $file_name = $base_name;
827 for (my $a = 1; (-e $file_name) && ($a < 1000); $a++) {
828 $file_name = "$base_name.dup_$a";
831 die("$progname: $base_name: File already exists, and ran " .
832 "out of attempts to create unique file name\n");
834 if ($Opt{'verbose
'}) {
835 warn("$progname: $base_name: File already exists, using " .
836 "unique name \"$file_name\" instead\n");
839 if (open(my $to_fp, ">", $file_name)) {
840 print_header(*$to_fp);
848 ) || die("$progname: $file_name: Cannot write to file: $!\n");
849 print_footer(*$to_fp);
851 if ($Opt{'output
-format
'} eq "gpsml") {
852 printf("<include>%s</include>\n",
853 txt_to_xml($file_name));
854 } elsif ($Opt{'output
-format
'} eq "gpx") {
855 printf("<!-- Saved unconverted data to \"%s\" -->\n",
856 txt_to_xml($file_name));
858 print("$progname: Saved unconverted data to \"$file_name\"\n");
862 die("$progname: $file_name: Cannot create file: $!\n");
870 if ($Dat{'what
'} eq "tp") {
872 if ($Opt{'require'}) {
873 $Req{'time'} && !$print_time && return;
874 $Req{'position
'} && !$print_pos && return;
875 $Req{'ele
'} && !$print_ele && return;
878 if ($Opt{'inside
'} || $Opt{'outside
'}) {
880 ($Dat{'lat
'} < $lat1) ||
881 ($Dat{'lat
'} > $lat2) ||
882 ($Dat{'lon
'} < $lon1) ||
883 ($Dat{'lon
'} > $lon2)
885 $Opt{'inside
'} && return;
887 $Opt{'outside
'} && return;
891 if ($Opt{'output
-format
'} eq "ps") {
898 && ($Dat{'lon
'} eq $last_lon)
899 && ($Dat{'lat
'} eq $last_lat)
901 if ($Opt{'output
-format
'} eq 'gpsml
') {
902 $Dat{'error
'} = "dup";
911 $Opt{'create
-breaks
'}
912 && $ep_time-$last_time > $PAUSE_LIMIT
915 $pause_len = $ep_time-$last_time;
916 # D("pause_len set to '$pause_len'");
919 $Line .= pause_entry($pause_len, $ep_time, $last_time);
924 # Valid data was found, send to stdout {{{
925 unless ($first_time) {
926 $first_time = $ep_time;
928 $Line .= gen_entry($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat);
932 if (!$last_time && $Opt{'output
-format
'} eq "ps") {
933 $Line .= "$Dat{'lon
'} $Dat{'lat
'} m\n";
938 if ($Opt{'output
-format
'} eq "gpsml") {
939 $Line = "<break/>\n$Line";
941 (!$pause_len && ($Opt{'output
-format
'} eq "xgraph"))
942 && ($Line .= "move $Line");
943 ($Opt{'output
-format
'} eq "clean") && ($Line .= "\n");
944 if ($Opt{'output
-format
'} eq "gpx") {
945 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n" .
946 "$Spc$Spc$Spc$Spc<trkseg>\n";
952 $print_time && ($last_time = $ep_time);
954 $last_lon = $Dat{'lon
'};
955 $last_lat = $Dat{'lat
'};
957 $last_line = $data_line;
958 $svg_start_thing = "\"/>\n";
963 # Generate trackpoint entry, calls trackpoint() {{{
964 my ($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat) = @_;
966 if ($Opt{'double
-y
-scale
'} && length($Dat{'lat
'})) {
969 if ($Opt{'output
-format
'} eq "gpsml") {
970 if ($Dat{'what
'} eq "tp") {
971 $Dat{'format
'} = "gpsml";
972 $Line .= trackpoint(%Dat);
973 } elsif ($Dat{'what
'} =~ /^(pause|desc|title)$/) {
974 $Line .= sprintf("<%s>%s</%s>\n",
979 } elsif ($Opt{'output
-format
'} eq "pgtab") {
980 if ($Dat{'what
'} eq "tp" && !length($Dat{'error
'})) {
981 $Dat{'format
'} = "pgtab";
982 $Line .= trackpoint(%Dat);
984 } elsif ($Opt{'output
-format
'} eq "xgraph") {
985 if ($print_pos && !length($Dat{'error
'})) {
986 $Dat{'format
'} = "xgraph";
987 $Line .= trackpoint(%Dat);
989 } elsif($Opt{'output
-format
'} eq "gpstrans") {
990 if ($print_pos && !length($Dat{'error
'})) {
991 $Dat{'format
'} = "gpstrans";
992 $Line .= trackpoint(%Dat);
994 } elsif($Opt{'output
-format
'} eq "gpx") {
995 if ($Dat{'what
'} eq "tp") {
996 $Dat{'format
'} = "gpx";
997 $Line .= trackpoint(%Dat);
999 } elsif ($Opt{'output
-format
'} eq "clean") {
1000 if ($Dat{'what
'} eq "tp" && !length($Dat{'error
'})) {
1001 $Dat{'format
'} = "clean";
1002 $Line .= trackpoint(%Dat);
1004 } elsif ($Opt{'output
-format
'} eq "ps") {
1007 ? "f\n$Dat{'lon
'} $Dat{'lat
'} m\n"
1008 : "$Dat{'lon
'} $Dat{'lat
'} l\n"
1010 } elsif ($Opt{'output
-format
'} eq "svg") {
1012 ($last_lon == 1000) || $pause_len
1014 "$svg_start_thing<path\n",
1015 " stroke=\"blue\"\n",
1016 " stroke-width=\"0.001\"\n",
1019 "M $Dat{'lon
'} $Dat{'lat
'}\n")
1020 : "L $Dat{'lon
'} $Dat{'lat
'}\n"
1022 } elsif ($Opt{'output
-format
'} eq "ygraph") {
1023 if (!length($Dat{'error
'})) {
1024 $Dat{'lat
'} = num_expand($Dat{'lat
'});
1025 $Dat{'lon
'} = num_expand($Dat{'lon
'});
1026 $Dat{'ele
'} = num_expand($Dat{'ele
'});
1027 my $Time = $print_time ? ($ep_time - $first_time) * 1 : 0;
1028 $Line .= "\"Time = $Time.0\n$Dat{'lon
'} $Dat{'lat
'}\n\n";
1030 } elsif ($Opt{'output
-format
'} eq "csv") {
1032 if (!length($Dat{'error
'})) {
1033 $Dat{'format
'} = "csv";
1034 $Dat{'lat
'} = num_expand($Dat{'lat
'});
1035 $Dat{'lon
'} = num_expand($Dat{'lon
'});
1036 $Dat{'ele
'} = num_expand($Dat{'ele
'});
1041 : $Opt{'short
-date
'}
1042 ? "$Dat{'year
'}$Dat{'month
'}$Dat{'day
'}T" .
1043 "$Dat{'hour
'}$Dat{'min
'}$Dat{'sec
'}Z"
1044 : "$Dat{'year
'}-$Dat{'month
'}-$Dat{'day
'}T" .
1045 "$Dat{'hour
'}:$Dat{'min
'}:$Dat{'sec
'}Z"
1049 $print_ele ? $Dat{'ele
'} : "", # Elevation
1054 } elsif ($Opt{'output
-format
'} eq "pgwtab") {
1055 # FIXME: NOP at the moment.
1057 die("$progname: \"$Opt{'output
-format
'}\": " .
1058 "Unknown output format\n");
1066 my ($pause_len, $ep_time, $last_time) = @_;
1069 if ($Opt{'output
-format
'} eq "gpsml") {
1070 $Line .= sprintf("<pause>%s</pause>\n",
1071 sec_to_readable($ep_time-$last_time));
1072 } elsif ($Opt{'output
-format
'} eq "clean") {
1073 $pause_len && ($Line .= "\n");
1074 } elsif ($Opt{'output
-format
'} eq "csv") {
1075 $Line .= sprintf("# Pause: %s\n# move\n",
1076 sec_to_readable($ep_time-$last_time));
1077 } elsif ($Opt{'output
-format
'} eq "xgraph") {
1078 $pause_len && ($Line .= "move ");
1086 # Send a Postscript header to stdout {{{
1087 my ($bl_lon, $bl_lat, $br_lon, $br_lat) = @_;
1088 my $Date = sec_to_string(time);
1090 "%!PS-Adobe-3.0 EPSF-3.0\n",
1091 "%%Creator: gpst\n",
1093 "%%CreationDate: $Date\n",
1094 "%%BoundingBox: $bl_lon $bl_lat $br_lon $br_lat\n",
1095 "%%DocumentData: Clean7Bit\n",
1098 "/bd { bind def } bind def\n",
1099 "/incompound false def\n",
1100 "/m { moveto } bd\n",
1101 "/l { lineto } bd\n",
1102 "/c { curveto } bd\n",
1103 "/F { incompound not {fill} if } bd\n",
1104 "/f { closepath F } bd\n",
1105 "/S { stroke } bd\n",
1106 "/*u { /incompound true def } bd\n",
1107 "/*U { /incompound false def f} bd\n",
1108 "/k { setcmykcolor } bd\n",
1118 # Print program version {{{
1119 print("$progname v$VERSION\n");
1124 # Send the help message to stdout {{{
1127 if ($Opt{'verbose
'}) {
1133 Converts between various GPS formats.
1135 Usage: $progname [options] [file [files [...]]]
1136 $progname -S [file [files [...]]]
1137 $progname -u [file [files [...]]]
1142 Check for broken chronology, warn about entries with an old
1145 Skip duplicated coordinates.
1147 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1149 Comment out entries which is obviously wrong. Use together with
1150 --chronology to fix those kind of errors. Does not work with GPX
1153 Used by the pgwupd format. Specifies from which date waypoints
1154 should be updated. No checks for valid date format here, let
1155 PostgreSQL take care of that. All variants it understands can be
1160 Print only trackpoints inside a rectangle specified by --pos1 and
1163 Use x as undefined value. Default: "$Udef".
1164 -o, --output-format x
1165 Use output format x:
1180 Print only trackpoints outside a rectangle specified by --pos1 and
1184 Specifies one corner where x is in "lat,lon" format (decimal
1185 degrees, negative for west or south) of area rectangle used by the
1186 --inside and --outside options.
1188 Specify requirements for trackpoints to be written. x is a string
1189 with the following flags:
1191 Print only waypoints which have an elevation.
1193 Print only waypoints which have a position.
1195 Print only waypoints which have a timestamp.
1196 -R, --round x=y[,x2=y2[...]]
1197 Round trackpoint element x to y decimals. Example:
1198 --round lat=4,lon=5,ele=1
1200 Use short date format.
1201 -S, --save-to-file x
1202 Save the unconverted data to a file with a filename starting with
1203 the timestamp of the first trackpoint. The parameter string x is
1204 added at the end of the filename. For the time being this option
1205 will ignore all other options. Note: If several files are specified
1206 on the command line, all data will be saved into only one file. This
1207 behaviour may change in the future.
1209 Create breaks in track between points with a difference more than
1210 $PAUSE_LIMIT seconds.
1211 -T x, --time-shift x
1212 Move time of trackpoint x seconds forwards or backwards. x can be a
1213 positive or negative integer.
1215 Increase level of verbosity. Can be repeated.
1217 Print version information.
1218 -w, --strip-whitespace
1219 Strip all unnecessary whitespace.
1220 -y, --double-y-scale
1221 Double Y scale (latitude) to get it right in gnuplot.
1223 Print debugging messages.
1231 # Print a status message to stderr based on verbosity level {{{
1232 my ($verbose_level, $Txt) = @_;
1234 if ($Opt{'verbose'} >= $verbose_level) {
1235 print(STDERR
"$progname: $Txt\n");
1243 # Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1245 This program is free software
: you can redistribute it
and/or modify it
1246 under the terms of the GNU General Public License as published by the
1247 Free Software Foundation
, either version
3 of the License
, or (at your
1248 option
) any later version
.
1250 This program is distributed
in the hope that it will be useful
, but
1251 WITHOUT ANY WARRANTY
; without even the implied warranty of
1252 MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE
.
1253 See the GNU General Public License
for more details
.
1255 You should have received a copy of the GNU General Public License along
1257 If
not, see L
<http
://www
.gnu
.org
/licenses/>.
1260 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :