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 #=======================================================================
16 use Time
::Local qw
{ timegm_nocheck
};
19 push(@INC, "$ENV{'HOME'}/bin/src/gpstools");
33 # Initial values for command line arguments {{{
38 'double-y-scale' => 0,
44 'output-format' => "gpsml",
50 'save-to-file' => "\n", # \n = undefined, it’s banned in filenames anyway.
53 'strip-whitespace' => 0,
63 $progname =~ s/^.*\/(.*?)$/$1/;
64 our $VERSION = "0.00";
66 Getopt
::Long
::Configure
("bundling");
68 # Command line options {{{
70 "chronology" => \
$Opt{'chronology'},
71 "create-breaks|t" => \
$Opt{'create-breaks'},
72 "debug" => \
$Opt{'debug'},
73 "double-y-scale|y" => \
$Opt{'double-y-scale'},
74 "epoch|e" => \
$Opt{'epoch'},
75 "fix" => \
$Opt{'fix'},
76 "from-date=s" => \
$Opt{'from-date'},
77 "help|h" => \
$Opt{'help'},
78 "inside" => \
$Opt{'inside'},
79 "output-format|o=s" => \
$Opt{'output-format'},
80 "outside" => \
$Opt{'outside'},
81 "pos1=s" => \
$Opt{'pos1'},
82 "pos2=s" => \
$Opt{'pos2'},
83 "require|r=s" => \
$Opt{'require'},
84 "round|R=s" => \
$Opt{'round'},
85 "save-to-file|S=s" => \
$Opt{'save-to-file'},
86 "short-date|s" => \
$Opt{'short-date'},
87 "skip-dups|d" => \
$Opt{'skip-dups'},
88 "strip-whitespace|w" => \
$Opt{'strip-whitespace'},
89 "time-shift|T=i" => \
$Opt{'time-shift'},
90 "undefined|n=s" => \
$Opt{'undefined'},
91 "verbose|v+" => \
$Opt{'verbose'},
92 "version" => \
$Opt{'version'},
95 ) || die("$progname: Option error. Use -h for help.\n");
99 my $PAUSE_LIMIT = 2 * 60; # Antall sekunder mellom to punkter det må til før en move legges inn.
101 my $DIGIT = '[0-9\.\-\+]'; # Used in regexps
102 $GPST::Spc
= $Opt{'strip-whitespace'} ?
"" : " ";
103 my $Spc = $GPST::Spc
; # FIXME
104 my $found_move = 0; # Settes til 1 hvis en /^# move$/ blir funnet.
107 my ($last_lon, $last_lat, $last_line) =
108 ( 1000, 1000, ""); # Vi kan jo teoretisk sett være i Greenwich eller på ekvator
109 my ($lat1, $lon1, $lat2, $lon2) =
110 (-1000, -1000, 1000, 1000);
113 'gpsbabel' => '/usr/local/bin/gpsbabel',
118 if ($Opt{'output-format'} =~ /^(gpx|pgtab)$/) {
119 $Opt{'require'} .= "p";
122 'ele' => ($Opt{'require'} =~ /e/) ?
1 : 0,
123 'position' => ($Opt{'require'} =~ /p/) ?
1 : 0,
124 'time' => ($Opt{'require'} =~ /t/) ?
1 : 0,
126 $Opt{'require'} =~ /[^ept]/
127 && die("$0: Unknown flag in --require (-r) value\n");
129 $Opt{'debug'} && ($Debug = 1);
130 $Opt{'help'} && usage
(0);
131 if ($Opt{'version'}) {
136 if ($Opt{'pos1'} =~ /^($DIGIT+),($DIGIT+)$/) {
140 if ($Opt{'pos2'} =~ /^($DIGIT+),($DIGIT+)$/) {
155 if ($Opt{'epoch'} && $Opt{'short-date'}) {
156 die("$progname: Cannot mix the --epoch (-e) and --short-date (-s) options\n");
159 if ($Opt{'inside'} && $Opt{'outside'}) {
160 die("$progname: Cannot mix the --inside and --outside options\n");
163 # To avoid printing out extra "/> at the start of svg output:
164 my $svg_start_thing = "";
168 if (defined($Opt{'round'})) {
169 my $R = $Opt{'round'};
170 $R =~ s/([a-z]+)=(\d+)/($Round{$1}=$2, "")/eg;
173 length($Opt{'undefined'}) && ($Udef = $Opt{'undefined'});
175 $Opt{'save-to-file'} eq "\n" && print_header
(*STDOUT
);
182 my $from_stdin = scalar(@ARGV) ?
0 : 1;
184 $from_stdin && push(@ARGV, "-");
186 for $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' => '',
207 $Opt{'epoch'} && ($Dat{'date-format'} = "epoch");
208 $Opt{'short-date'} && ($Dat{'date-format'} = "short");
210 if ($Opt{'save-to-file'} ne "\n") {
211 push(@first_lines, $_);
213 s/^# error // && ($Dat{'error'} = "error");
214 s/^# ?// && ($Dat{'error'} = "desc");
216 if (m
#^<(e?tp)\b(.*?)>(.*?)</(e?tp)>\s*$#) {
217 # gpsml — The main storage format {{{
218 my ($Elem, $Props, $Data) =
220 my $err_str = ($Props =~ /\berr="(.*?)"/) ?
$1 : "error";
221 $Elem eq "etp" && ($Dat{'error'} = $err_str);
223 $Data =~ m
#<time>(.*?)</time># && ($Time = $1);
225 (\d\d\d\d
)-?
(\d\d
)-?
(\d\d
)[T
](\d\d
):?
(\d\d
):?
([\d\
.]+?
)Z
227 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
228 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
233 $Data =~ m
#<lat>($DIGIT*?)</lat># && ($Dat{'lat'} = $1);
234 $Data =~ m
#<lon>($DIGIT*?)</lon># && ($Dat{'lon'} = $1);
235 $Data =~ m
#<ele>($DIGIT*?)</ele># && ($Dat{'ele'} = $1);
236 $Data =~ m
#<desc>(.*?)</desc># && ($Dat{'desc'} = $1);
239 } elsif (m
#^<break\b.*?/>#) {
241 } elsif (m
#^<(title|pause)\b.*?>(.*?)</(title|pause)>#) {
245 } elsif (m
#^<desc\b.*?>(.*$)#s) {
246 $Dat{'what'} = "desc";
248 until ($Txt =~ m
#</desc>#s) {
251 $Txt =~ s
#^(.*)(</desc>.*$)#$1#s;
256 $xml_data .= join("", <$curr_fp>);
257 if (!length($Opt{'output-format'})) {
258 $Opt{'output-format'} = "gpx";
259 print_header
(*STDOUT
);
261 read_xmlfile
($xml_data);
265 } elsif (m
#^(\d+)\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
266 # CSV format, epoch style {{{
267 my ($ep_time, $lon_val, $lat_val, $Alt) =
269 $Dat{'epoch'} = $ep_time;
270 ($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
271 $Dat{'day'}, $Dat{'month'}, $Dat{'year'},
272 $Dat{'wday'}, $Dat{'yday'}) = gmtime($ep_time);
273 $Dat{'month'}++; # Urgh Ⅰ
274 $Dat{'year'} += 1900; # Urgh Ⅱ
279 (\d\d\d\d
)-?
(\d\d
)-?
(\d\d
)[T\
](\d\d
):?
(\d\d
):?
(\d\d
)Z?
\t
280 ($DIGIT+)\t($DIGIT+)\t($DIGIT)
283 # CSV format, human-readable date format {{{
284 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
285 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
286 $Dat{'lon'}, $Dat{'lat'}, $Dat{'ele'}) =
292 } elsif (/^Trackpoint\t/) {
293 # 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 {{{
296 # N60.41630 E5.31675\t
297 # 09.02.2006 20:24:37 (UTC)\t
305 $Orig =~ s/[\r\n]+$//;
306 my ($Marker_f, $Position_f, $Time_f, $Alt_f, $Depth_f,
307 $Leglength_f, $Legtime_f, $Legspeed_f, $Legcourse_f) =
309 # Nødløsning for å unngå at variabler
311 "\t\t\t\t\t\t\t\t\t\t"
314 # "Position_f=\"$Position_f\" \x7B\x7B\x7B\n",
315 # "Time_f=\"$Time_f\"\n",
316 # "Alt_f=\"$Alt_f\"\n",
317 # "Depth_f=\"$Depth_f\"\n",
318 # "Leglength_f=\"$Leglength_f\"\n",
319 # "Legtime_f=\"$Legtime_f\"\n",
320 # "Legspeed_f=\"$Legspeed_f\"\n",
321 # "Legcourse_f=\"$Legcourse_f\" \x7D\x7D\x7D\n",
326 $Legtime_hour, $Legtime_min, $Legtime_sec,
327 $Legspeed, $Legspeed_unit,
329 ) = ("", "", "", "", "", "", "", "", "", "", "", "", "",
330 "", "", "", "", "", "", "", "", "", "");
331 ($Position_f =~ /^(N|S)([\d\.]+) (W|E)([\d\.]+)/) &&
332 ($NS = $1, $Dat{'lat'} = $2, $WE = $3, $Dat{'lon'} = $4);
333 ($Time_f =~ /^(\d+)\.(\d+)\.(\d+) (\d+):(\d+):(\d+) \((.+?)\)/) &&
334 ($Dat{'day'} = $1, $Dat{'month'} = $2, $Dat{'year'} = $3,
335 $Dat{'hour'} = $4, $Dat{'min'} = $5, $Dat{'sec'} = $6);
336 ($Alt_f =~ /^($DIGIT+) (.*?)/) &&
337 ($Dat{'ele'} = $1, $Alt_unit = $2);
338 # D("ele = \"$Dat{'ele'}\"");
339 ($NS eq "S") && ($Dat{'lat'} = 0-$Dat{'lat'});
340 ($WE eq "W") && ($Dat{'lon'} = 0-$Dat{'lon'});
341 # MapSource in win xp writes YYYY, but YY in win98se.
343 defined($Dat{'year'})
344 && $Dat{'year'} =~ /\d/
345 && $Dat{'year'} < 1900
346 ) && ($Dat{'year'} += 2000);
349 } elsif (/^Track\t(.*?)\t/) {
350 $Dat{'title'} = txt_to_xml
($1);
351 $Dat{'what'} = "title";
357 (\d\d
)/(\d\d)/(\d\d\d\d
)\
(\d\d
):(\d\d
):(\d\d
)\t
362 # T 09/01/2002 11:51:26 60°23'36.3" 5°
19'35.9" {{{
363 my ($lat_d, $lat_m, $lat_s, $lon_d, $lon_m, $lon_s);
364 ($Dat{'month
'}, $Dat{'day
'}, $Dat{'year
'},
365 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'},
366 $lat_d, $lat_m, $lat_s,
367 $lon_d, $lon_m, $lon_s) =
372 my $Flat = defined($Round{'lat
'}) ? ".$Round{'lat
'}" : "";
373 my $Flon = defined($Round{'lon
'}) ? ".$Round{'lon
'}" : "";
374 $Dat{'lat
'} = sprintf("%${Flat}f",
375 1.0*($lat_d+($lat_m/60)+($lat_s/3600)));
376 $Dat{'lon
'} = sprintf("%${Flon}f",
377 1.0*$lon_d+($lon_m/60)+($lon_s/3600));
382 1\ (\S+)\ (\S+)\ (\S+)\ (\S+)\x20
383 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)
386 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
387 ($Dat{'lat
'}, $Dat{'lon
'}, $Dat{'speed
'},
389 $Dat{'month
'}, $Dat{'day
'}, $Dat{'year
'},
390 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}) =
398 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
408 (\d\d) # Latitude degree
409 (\d\d) # Latitude minute
410 (\d\d\d) # Latitude minute decimals
412 (\d\d\d) # Longitude degree
413 (\d\d) # Longitude minute
414 (\d\d\d) # Longitude minute degree
420 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
421 $lon_degmin, $lon_mindec);
422 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'}, $Dat{'hour
'},
423 $Dat{'min
'}, $Dat{'sec
'}, $NS, $lat_deg,
424 $lat_degmin, $lat_mindec, $EW,
425 $lon_deg, $lon_degmin, $lon_mindec,
426 $Dat{'accur
'}, $Dat{'ele
'}, $Dat{'unknown
'}) =
427 ($2+2000, $3, $4, $5,
432 my $ep_time = timegm_nocheck(
433 $Dat{'sec
'}, $Dat{'min
'}, $Dat{'hour
'},
434 $Dat{'day
'}, $Dat{'month
'}-1, $Dat{'year
'}
436 $last_time = $ep_time;
437 my $Flat = defined($Round{'lat
'}) ? ".$Round{'lat
'}" : "";
438 my $Flon = defined($Round{'lon
'}) ? ".$Round{'lon
'}" : "";
439 my $tmp_lon = sprintf(
444 my $tmp_lat = sprintf("%${Flat}f",
448 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
449 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
450 $Dat{'lat
'} = $tmp_lat;
451 $Dat{'lon
'} = $tmp_lon;
454 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(_{42})/) {
455 # @020721221336__________________________________________ {{{
456 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'},
457 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}, $Dat{'rest
'}) =
460 $Dat{'error
'} = "nosignal";
463 } elsif (/^xmaplog /) {
466 ($Opt{'output
-format
'} eq "csv")
467 && ($Opt{'save
-to
-file
'} eq "\n")
469 } elsif (/^Pause: /) {
470 # NOP, is here to cope with old files I’ve lying around.
471 } elsif ($Dat{'error
'} eq "desc") {
473 if (defined($Comment)) {
474 $Comment =~ s/^\s*(.*?)\s*$/$1/;
475 if ($Opt{'output
-format
'} eq "gpsml") {
476 $Dat{'desc
'} = txt_to_xml($Comment);
477 $Dat{'what
'} = "desc";
482 $Opt{'verbose
'} && warn("Line $.: Unknown: \"$_\"\n");
487 warn("$progname: $curr_file: Cannot open file for read: $!\n");
492 print_footer(*STDOUT);
498 my $Txt = join("", @_);
499 $Txt =~ s/<!--(.*?)-->//gs;
500 $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
508 # D("print_xml_gps(\"$Orig\")\n");
509 $Str =~ s/<!--(.*?)-->//gs;
510 my $fromdate_str = "";
511 if ($Opt{'from
-date
'}) {
512 $fromdate_str = "date >= '$Opt{'from-date'}' AND ";
514 if ($Opt{'output
-format
'} =~ /^(pgwtab|pgwupd)$/) {
518 <wpt\b(.*?)>(.*?)</wpt>
523 my ($Lat, $Lon, $Name, $Ele, $Type, $Time, $Cmt, $Desc, $Src, $Sym) =
524 ('\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
', '\N
');
526 $attr_wpt =~ /.*lat="($DIGIT+?)"/s &&
527 ($Lat = postgresql_copy_safe($1));
528 $attr_wpt =~ /.*lon="($DIGIT+?)"/s &&
529 ($Lon = postgresql_copy_safe($1));
530 $el_wpt =~ /.*<name\b(.*?)>(.*?)<\/name>/s &&
531 ($Name = postgresql_copy_safe(xml_to_txt($2)));
532 $el_wpt =~ /.*<ele\b(.*?)>(.*?)<\/ele>/s &&
533 ($Ele = postgresql_copy_safe(xml_to_txt($2)));
534 $el_wpt =~ /.*<type\b(.*?)>(.*?)<\/type>/s &&
535 ($Type = postgresql_copy_safe(xml_to_txt($2)));
536 $el_wpt =~ /.*<time\b(.*?)>(.*?)<\/time>/s &&
537 ($Time = postgresql_copy_safe(xml_to_txt($2)));
538 $el_wpt =~ /.*<cmt\b(.*?)>(.*?)<\/cmt>/s &&
539 ($Cmt = postgresql_copy_safe(xml_to_txt($2)));
540 $el_wpt =~ /.*<desc\b(.*?)>(.*?)<\/desc>/s &&
541 ($Desc = postgresql_copy_safe(xml_to_txt($2)));
542 $el_wpt =~ /.*<src\b(.*?)>(.*?)<\/src>/s &&
543 ($Src = postgresql_copy_safe(xml_to_txt($2)));
544 $el_wpt =~ /.*<sym\b(.*?)>(.*?)<\/sym>/s &&
545 ($Sym = postgresql_copy_safe(xml_to_txt($2)));
547 if (length($Opt{'round
'})) {
548 if (defined($Round{'lat
'}) && length($Lat)) {
549 ($Lat = 1.0 * sprintf("%.$Round{'lat
'}f", $Lat));
551 if (defined($Round{'lon
'}) && length($Lon)) {
552 ($Lon = 1.0 * sprintf("%.$Round{'lon
'}f", $Lon));
554 if (defined($Round{'ele
'}) && $Ele ne '\N
') {
555 ($Ele = 1.0 * sprintf("%.$Round{'ele
'}f", $Ele));
559 if ($Opt{'output
-format
'} eq "pgwtab") {
573 } elsif ($Opt{'output
-format
'} eq "pgwupd") {
577 "$Spc${Spc}UPDATE logg SET name = clname(coor) " .
578 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
579 "$Spc${Spc}UPDATE logg SET dist = cldist(coor) " .
580 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
591 <trk\b(.*?)>(.*?)</trk>
597 <name\b(.*?)>(.*?)</name>
600 $tmp_dat{'title
'} = $2;
601 $tmp_dat{'what
'} = "title";
602 $tmp_dat{'error
'} = "";
603 print_entry(%tmp_dat);
608 <trkseg\b(.*?)>(.*?)</trkseg>
614 <trkpt\b(.*?)>(.*?)</trkpt>
617 my ($attr_trkpt, $el_trkpt) =
620 'year
' => '', 'month
' => '', 'day
' => '',
621 'hour
' => '', 'min
' => '', 'sec
' => '',
624 'lat
' => '', 'lon
' => '',
630 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon
'} = $1);
631 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat
'} = $1);
632 ($el_trkpt =~ m#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele
'} = $1);
636 <time>(\d\d\d\d)-?(\d\d)-?(\d\d)T
637 (\d\d):?(\d\d):?([\d\.]+)Z</time>
640 ($Dat{'year
'}, $Dat{'month
'}, $Dat{'day
'},
641 $Dat{'hour
'}, $Dat{'min
'}, $Dat{'sec
'}) =
642 ($1, $2, $3, $4, $5, $6);
659 if ($Opt{'output
-format
'} eq "gpsml") {
660 print($out_fp join("",
661 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
665 } elsif ($Opt{'output
-format
'} eq "gpstrans") {
666 print($out_fp "Format: DMS UTC Offset: 0.00 hrs " .
667 "Datum[100]: WGS 84\n");
668 } elsif ($Opt{'output
-format
'} eq "gpx") {
669 print($out_fp join("",
670 qq{<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n},
672 qq{$Spc${Spc}version="1.1"\n},
673 qq{$Spc${Spc}creator="gpst - http://sunny256.github.com/gpstools/"\n},
674 qq{$Spc${Spc}xmlns="http://www.topografix.com/GPX/1/1"\n},
675 qq{$Spc${Spc}xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n},
676 qq{$Spc${Spc}xsi:schemaLocation="http://www.topografix.com/GPX/1/1 },
677 qq{http://www.topografix.com/GPX/1/1/gpx.xsd"\n},
680 qq{$Spc$Spc$Spc$Spc<trkseg>\n},
682 } elsif ($Opt{'output
-format
'} eq "ps") {
683 print($out_fp ps_header(532, 6034, 533, 6040));
684 print($out_fp "*u\n");
685 } elsif ($Opt{'output
-format
'} eq "svg") {
686 print($out_fp join("",
687 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
688 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
689 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
690 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
691 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
692 "$Spc$Spc<title></title>\n",
693 "$Spc$Spc<desc></desc>\n",
702 if ($Opt{'output
-format
'} eq "gpsml") {
703 print($out_fp join("",
707 } elsif ($Opt{'output
-format
'} eq "gpx") {
708 print($out_fp join("",
709 "$Spc$Spc$Spc$Spc</trkseg>\n",
713 } elsif ($Opt{'output
-format
'} eq "poscount") {
714 while (my ($l_name, $l_val) = each %Poscount) {
715 $l_name =~ /^(.+?),(.+?)$/
716 && print($out_fp "$1\t$2\t$l_val\n");
718 } elsif ($Opt{'output
-format
'} eq "ps") {
719 print($out_fp join("",
724 } elsif ($Opt{'output
-format
'} eq "svg") {
725 print($out_fp "\"/>\n</svg>\n");
731 # Print a GPS entry with time, latitude, longitude and elevation in
735 defined($Dat{'desc
'}) || ($Dat{'desc
'} = "");
736 defined($Dat{'ele
'}) || ($Dat{'ele
'} = "");
737 defined($Dat{'lat
'}) || ($Dat{'lat
'} = "");
738 defined($Dat{'lon
'}) || ($Dat{'lon
'} = "");
739 defined($Dat{'year
'}) || ($Dat{'year
'} = "");
740 my $print_time = length($Dat{'year
'}) ? 1 : 0;
742 if (!$Req{'position
'} && $Opt{'output
-format
'} eq "gpsml") {
743 $print_pos = (length($Dat{'lat
'}) || length($Dat{'lon
'})) ? 1 : 0;
745 $print_pos = (length($Dat{'lat
'}) && length($Dat{'lon
'})) ? 1 : 0;
748 $Dat{'lat
'} = $Dat{'lon
'} = "";
750 my $print_ele = length($Dat{'ele
'}) ? 1 : 0;
751 my $print_desc = length($Dat{'desc
'}) ? 1 : 0;
753 # D("print_entry(\"" . join("\", \"", @_) . "\");");
756 if (length($Opt{'round
'})) {
757 for my $Tmp (qw{ lat lon ele }) {
758 if (defined($Round{$Tmp}) && length($Dat{$Tmp})) {
760 ($Dat{$Tmp} = num_expand(1.0 * sprintf("%.$Round{$Tmp}f", $Dat{$Tmp})));
765 if ($Opt{'output
-format
'} eq "poscount") {
766 # FIXME: Sort output in some way
767 if (!length($Dat{'error
'})) {
768 $Dat{'lat
'} = num_expand($Dat{'lat
'});
769 $Dat{'lon
'} = num_expand($Dat{'lon
'});
770 my $Name = "$Dat{'lon
'},$Dat{'lat
'}";
771 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
778 $ep_time = timegm_nocheck(
779 $Dat{'sec
'}, $Dat{'min
'}, $Dat{'hour
'},
780 $Dat{'day
'}, $Dat{'month
'} - 1, $Dat{'year
'}
782 if ($Opt{'time-shift'}) {
783 # D("ep_time før: '$ep_time'");
784 $ep_time += $Opt{'time-shift'};
785 # D("ep_time etter: '$ep_time'");
786 ($Dat{'sec
'}, $Dat{'min
'},$Dat{'hour
'}, $Dat{'day
'},
787 $Dat{'month
'}, $Dat{'year
'}) = gmtime($ep_time);
788 $Dat{'year
'} += 1900;
791 $Dat{'epoch
'} = $ep_time;
792 $Dat{'year
'} = sprintf("%04u", $Dat{'year
'});
793 $Dat{'month
'} = sprintf("%02u", $Dat{'month
'});
794 $Dat{'day
'} = sprintf("%02u", $Dat{'day
'});
795 $Dat{'hour
'} = sprintf("%02u", $Dat{'hour
'});
796 $Dat{'min
'} = sprintf("%02u", $Dat{'min
'});
797 $Dat{'sec
'} = sprintf("%02u", $Dat{'sec
'});
798 if ($Opt{'chronology
'}) {
799 if ($last_time > $ep_time && !length($Dat{'error
'})) {
801 "%s: $curr_file: \"%sZ\": Next date is %s in the past (%sZ)\n",
802 $progname, sec_to_string($last_time, "T"),
803 sec_to_readable($last_time-$ep_time),
804 sec_to_string($ep_time, "T")
806 # FIXME: Make --fix work with gpx.
807 if ($Opt{'fix
'} && ($Opt{'output
-format
'} !~ /^gpx$/)) {
808 $Dat{'error
'} = "chrono";
810 } elsif ($last_time == $ep_time && !length($Dat{'error
'})) {
812 "%s: $curr_file: \"%sZ\": Duplicated time\n",
813 $progname, sec_to_string($last_time, "T")
815 # FIXME: Make --fix work with gpx.
816 if ($Opt{'fix
'} && ($Opt{'output
-format
'} !~ /^gpx$/)) {
817 $Dat{'error
'} = "duptime";
831 if ($Opt{'save
-to
-file
'} ne "\n") {
833 $print_time || return;
834 my $base_name = "$Dat{'year
'}$Dat{'month
'}$Dat{'day
'}T" .
835 "$Dat{'hour
'}$Dat{'min
'}$Dat{'sec
'}Z" .
836 "$Opt{'save
-to
-file
'}";
837 my $file_name = $base_name;
839 for (my $a = 1; (-e $file_name) && ($a < 1000); $a++) {
840 $file_name = "$base_name.dup_$a";
843 die("$progname: $base_name: File already exists, and ran " .
844 "out of attempts to create unique file name\n");
846 if ($Opt{'verbose
'}) {
847 warn("$progname: $base_name: File already exists, using " .
848 "unique name \"$file_name\" instead\n");
851 if (open(my $to_fp, ">", $file_name)) {
852 print_header(*$to_fp);
860 ) || die("$progname: $file_name: Cannot write to file: $!\n");
861 print_footer(*$to_fp);
863 if ($Opt{'output
-format
'} eq "gpsml") {
864 printf("<include>%s</include>\n",
865 txt_to_xml($file_name));
866 } elsif ($Opt{'output
-format
'} eq "gpx") {
867 printf("<!-- Saved unconverted data to \"%s\" -->\n",
868 txt_to_xml($file_name));
870 print("$progname: Saved unconverted data to \"$file_name\"\n");
874 die("$progname: $file_name: Cannot create file: $!\n");
882 if ($Dat{'what
'} eq "tp") {
884 if ($Opt{'require'}) {
885 $Req{'time'} && !$print_time && return;
886 $Req{'position
'} && !$print_pos && return;
887 $Req{'ele
'} && !$print_ele && return;
890 if ($Opt{'inside
'} || $Opt{'outside
'}) {
892 ($Dat{'lat
'} < $lat1) ||
893 ($Dat{'lat
'} > $lat2) ||
894 ($Dat{'lon
'} < $lon1) ||
895 ($Dat{'lon
'} > $lon2)
897 $Opt{'inside
'} && return;
899 $Opt{'outside
'} && return;
903 if ($Opt{'output
-format
'} eq "ps") {
910 && ($Dat{'lon
'} eq $last_lon)
911 && ($Dat{'lat
'} eq $last_lat)
913 if ($Opt{'output
-format
'} eq 'gpsml
') {
914 $Dat{'error
'} = "dup";
923 $Opt{'create
-breaks
'}
924 && $ep_time-$last_time > $PAUSE_LIMIT
927 $pause_len = $ep_time-$last_time;
928 # D("pause_len set to '$pause_len'");
931 $Line .= pause_entry($pause_len, $ep_time, $last_time);
936 # Valid data was found, send to stdout {{{
937 unless ($first_time) {
938 $first_time = $ep_time;
940 $Line .= gen_entry($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat);
944 if (!$last_time && $Opt{'output
-format
'} eq "ps") {
945 $Line .= "$Dat{'lon
'} $Dat{'lat
'} m\n";
950 if ($Opt{'output
-format
'} eq "gpsml") {
951 $Line = "<break/>\n$Line";
953 (!$pause_len && ($Opt{'output
-format
'} eq "xgraph"))
954 && ($Line .= "move $Line");
955 ($Opt{'output
-format
'} eq "clean") && ($Line .= "\n");
956 if ($Opt{'output
-format
'} eq "gpx") {
957 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n" .
958 "$Spc$Spc$Spc$Spc<trkseg>\n";
964 $print_time && ($last_time = $ep_time);
966 $last_lon = $Dat{'lon
'};
967 $last_lat = $Dat{'lat
'};
969 $last_line = $data_line;
970 $svg_start_thing = "\"/>\n";
975 # Generate trackpoint entry, calls trackpoint() {{{
976 my ($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat) = @_;
978 if ($Opt{'double
-y
-scale
'} && length($Dat{'lat
'})) {
981 if ($Opt{'output
-format
'} eq "gpsml") {
982 if ($Dat{'what
'} eq "tp") {
983 $Dat{'format
'} = "gpsml";
984 $Line .= trackpoint(%Dat);
985 } elsif ($Dat{'what
'} =~ /^(pause|desc|title)$/) {
986 $Line .= sprintf("<%s>%s</%s>\n",
991 } elsif ($Opt{'output
-format
'} eq "pgtab") {
992 if ($Dat{'what
'} eq "tp" && !length($Dat{'error
'})) {
993 $Dat{'format
'} = "pgtab";
994 $Line .= trackpoint(%Dat);
996 } elsif ($Opt{'output
-format
'} eq "xgraph") {
997 if ($print_pos && !length($Dat{'error
'})) {
998 $Dat{'format
'} = "xgraph";
999 $Line .= trackpoint(%Dat);
1001 } elsif($Opt{'output
-format
'} eq "gpstrans") {
1002 if ($print_pos && !length($Dat{'error
'})) {
1003 $Dat{'format
'} = "gpstrans";
1004 $Line .= trackpoint(%Dat);
1006 } elsif($Opt{'output
-format
'} eq "gpx") {
1007 if ($Dat{'what
'} eq "tp") {
1008 $Dat{'format
'} = "gpx";
1009 $Line .= trackpoint(%Dat);
1011 } elsif ($Opt{'output
-format
'} eq "clean") {
1012 if ($Dat{'what
'} eq "tp" && !length($Dat{'error
'})) {
1013 $Dat{'format
'} = "clean";
1014 $Line .= trackpoint(%Dat);
1016 } elsif ($Opt{'output
-format
'} eq "ps") {
1019 ? "f\n$Dat{'lon
'} $Dat{'lat
'} m\n"
1020 : "$Dat{'lon
'} $Dat{'lat
'} l\n"
1022 } elsif ($Opt{'output
-format
'} eq "svg") {
1024 ($last_lon == 1000) || $pause_len
1026 "$svg_start_thing<path\n",
1027 " stroke=\"blue\"\n",
1028 " stroke-width=\"0.001\"\n",
1031 "M $Dat{'lon
'} $Dat{'lat
'}\n")
1032 : "L $Dat{'lon
'} $Dat{'lat
'}\n"
1034 } elsif ($Opt{'output
-format
'} eq "ygraph") {
1035 if (!length($Dat{'error
'})) {
1036 $Dat{'lat
'} = num_expand($Dat{'lat
'});
1037 $Dat{'lon
'} = num_expand($Dat{'lon
'});
1038 $Dat{'ele
'} = num_expand($Dat{'ele
'});
1039 my $Time = $print_time ? ($ep_time - $first_time) * 1 : 0;
1040 $Line .= "\"Time = $Time.0\n$Dat{'lon
'} $Dat{'lat
'}\n\n";
1042 } elsif ($Opt{'output
-format
'} eq "csv") {
1044 if (!length($Dat{'error
'})) {
1045 $Dat{'format
'} = "csv";
1046 $Dat{'lat
'} = num_expand($Dat{'lat
'});
1047 $Dat{'lon
'} = num_expand($Dat{'lon
'});
1048 $Dat{'ele
'} = num_expand($Dat{'ele
'});
1053 : $Opt{'short
-date
'}
1054 ? "$Dat{'year
'}$Dat{'month
'}$Dat{'day
'}T" .
1055 "$Dat{'hour
'}$Dat{'min
'}$Dat{'sec
'}Z"
1056 : "$Dat{'year
'}-$Dat{'month
'}-$Dat{'day
'}T" .
1057 "$Dat{'hour
'}:$Dat{'min
'}:$Dat{'sec
'}Z"
1061 $print_ele ? $Dat{'ele
'} : "", # Elevation
1066 } elsif ($Opt{'output
-format
'} eq "pgwtab") {
1067 # FIXME: NOP at the moment.
1069 die("$progname: \"$Opt{'output
-format
'}\": " .
1070 "Unknown output format\n");
1078 my ($pause_len, $ep_time, $last_time) = @_;
1081 if ($Opt{'output
-format
'} eq "gpsml") {
1082 $Line .= sprintf("<pause>%s</pause>\n",
1083 sec_to_readable($ep_time-$last_time));
1084 } elsif ($Opt{'output
-format
'} eq "clean") {
1085 $pause_len && ($Line .= "\n");
1086 } elsif ($Opt{'output
-format
'} eq "csv") {
1087 $Line .= sprintf("# Pause: %s\n# move\n",
1088 sec_to_readable($ep_time-$last_time));
1089 } elsif ($Opt{'output
-format
'} eq "xgraph") {
1090 $pause_len && ($Line .= "move ");
1098 # Send a Postscript header to stdout {{{
1099 my ($bl_lon, $bl_lat, $br_lon, $br_lat) = @_;
1100 my $Date = sec_to_string(time);
1102 "%!PS-Adobe-3.0 EPSF-3.0\n",
1103 "%%Creator: gpst\n",
1105 "%%CreationDate: $Date\n",
1106 "%%BoundingBox: $bl_lon $bl_lat $br_lon $br_lat\n",
1107 "%%DocumentData: Clean7Bit\n",
1110 "/bd { bind def } bind def\n",
1111 "/incompound false def\n",
1112 "/m { moveto } bd\n",
1113 "/l { lineto } bd\n",
1114 "/c { curveto } bd\n",
1115 "/F { incompound not {fill} if } bd\n",
1116 "/f { closepath F } bd\n",
1117 "/S { stroke } bd\n",
1118 "/*u { /incompound true def } bd\n",
1119 "/*U { /incompound false def f} bd\n",
1120 "/k { setcmykcolor } bd\n",
1130 # Print program version {{{
1131 print("$progname v$VERSION\n");
1136 # Send the help message to stdout {{{
1139 if ($Opt{'verbose
'}) {
1145 Converts between various GPS formats.
1147 Usage: $progname [options] [file [files [...]]]
1148 $progname -S [file [files [...]]]
1149 $progname -u [file [files [...]]]
1154 Check for broken chronology, warn about entries with an old
1157 Skip duplicated coordinates.
1159 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1161 Comment out entries which is obviously wrong. Use together with
1162 --chronology to fix those kind of errors. Does not work with GPX
1165 Used by the pgwupd format. Specifies from which date waypoints
1166 should be updated. No checks for valid date format here, let
1167 PostgreSQL take care of that. All variants it understands can be
1172 Print only trackpoints inside a rectangle specified by --pos1 and
1175 Use x as undefined value. Default: "$Udef".
1176 -o, --output-format x
1177 Use output format x:
1192 Print only trackpoints outside a rectangle specified by --pos1 and
1196 Specifies one corner where x is in "lat,lon" format (decimal
1197 degrees, negative for west or south) of area rectangle used by the
1198 --inside and --outside options.
1200 Specify requirements for trackpoints to be written. x is a string
1201 with the following flags:
1203 Print only waypoints which have an elevation.
1205 Print only waypoints which have a position.
1207 Print only waypoints which have a timestamp.
1208 -R, --round x=y[,x2=y2[...]]
1209 Round trackpoint element x to y decimals. Example:
1210 --round lat=4,lon=5,ele=1
1212 Use short date format.
1213 -S, --save-to-file x
1214 Save the unconverted data to a file with a filename starting with
1215 the timestamp of the first trackpoint. The parameter string x is
1216 added at the end of the filename. For the time being this option
1217 will ignore all other options. Note: If several files are specified
1218 on the command line, all data will be saved into only one file. This
1219 behaviour may change in the future.
1221 Create breaks in track between points with a difference more than
1222 $PAUSE_LIMIT seconds.
1223 -T x, --time-shift x
1224 Move time of trackpoint x seconds forwards or backwards. x can be a
1225 positive or negative integer.
1227 Increase level of verbosity. Can be repeated.
1229 Print version information.
1230 -w, --strip-whitespace
1231 Strip all unnecessary whitespace.
1232 -y, --double-y-scale
1233 Double Y scale (latitude) to get it right in gnuplot.
1235 Print debugging messages.
1243 # Print a status message to stderr based on verbosity level {{{
1244 my ($verbose_level, $Txt) = @_;
1246 if ($Opt{'verbose'} >= $verbose_level) {
1247 print(STDERR
"$progname: $Txt\n");
1255 # Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1257 This program is free software
: you can redistribute it
and/or modify it
1258 under the terms of the GNU General Public License as published by the
1259 Free Software Foundation
, either version
3 of the License
, or (at your
1260 option
) any later version
.
1262 This program is distributed
in the hope that it will be useful
, but
1263 WITHOUT ANY WARRANTY
; without even the implied warranty of
1264 MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE
.
1265 See the GNU General Public License
for more details
.
1267 You should have received a copy of the GNU General Public License along
1269 If
not, see L
<http
://www
.gnu
.org
/licenses/>.
1272 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :