3 #=======================================================================
5 # Converts between various GPS formats
8 # ©opyleft 2002– Øyvind A. Holm <sunny@sunbase.org>
9 # License: GNU General Public License, see end of file for legal stuff.
10 #=======================================================================
14 use Time
::Local qw
{ timegm_nocheck
};
23 # Initial values for command line arguments {{{
25 'comment-out-dups' => 0,
28 'double-y-scale' => 0,
34 'output-format' => "gpsml",
38 'print-comments' => 0,
40 'save-to-file' => "\n", # \n = undefined, it’s banned in filenames anyway.
43 'strip-whitespace' => 0,
51 $progname =~ s
#^.*/(.*?)$#$1#;
54 my $id_date = $rcs_id;
55 $id_date =~ s/^.*?\d+ (\d\d\d\d-.*?\d\d:\d\d:\d\d\S+).*/$1/;
57 Getopt
::Long
::Configure
("bundling");
59 # Command line options {{{
60 "chronology" => \
$Opt{'chronology'},
61 "comment-out-dups|u" => \
$Opt{'comment-out-dups'},
62 "create-breaks|t" => \
$Opt{'create-breaks'},
63 "debug" => \
$Opt{'debug'},
64 "double-y-scale|y" => \
$Opt{'double-y-scale'},
65 "epoch|e" => \
$Opt{'epoch'},
66 "fix" => \
$Opt{'fix'},
67 "help|h" => \
$Opt{'help'},
68 "inside" => \
$Opt{'inside'},
69 "near" => \
$Opt{'near'},
70 "output-format|o=s" => \
$Opt{'output-format'},
71 "outside" => \
$Opt{'outside'},
72 "pos1=s" => \
$Opt{'pos1'},
73 "pos2=s" => \
$Opt{'pos2'},
74 "print-comments|C" => \
$Opt{'print-comments'},
75 "require|r=s" => \
$Opt{'require'},
76 "save-to-file|S=s" => \
$Opt{'save-to-file'},
77 "short-date|s" => \
$Opt{'short-date'},
78 "skip-dups|d" => \
$Opt{'skip-dups'},
79 "strip-whitespace|w" => \
$Opt{'strip-whitespace'},
80 "undefined|n=s" => \
$Opt{'undefined'},
81 "use-comma|c" => \
$Opt{'use-comma'},
82 "verbose|v" => \
$Opt{'verbose'},
83 "version" => \
$Opt{'version'},
85 ) || die("$progname: Option error. Use -h for help.\n");
89 my $PAUSE_LIMIT = 2 * 60; # Antall sekunder mellom to punkter det må til før en move legges inn.
90 my $Des = $Opt{'use-comma'} ?
"," : ".";
92 my $DIGIT = '[0-9\.\-\+]'; # Used in regexps
93 my $Spc = $Opt{'strip-whitespace'} ?
"" : " ";
94 my $in_dupskip = 0; # Er 1 hvis vi holder på med ignorering av duplikater
95 my $found_move = 0; # Settes til 1 hvis en /^# move$/ blir funnet.
98 my ($last_lon, $last_lat, $last_ele, $last_line) =
99 ( 1000, 1000, 100000, ""); # Vi kan jo teoretisk sett være i Greenwich eller på ekvator
100 my ($lat1, $lon1, $lat2, $lon2) =
101 (-1000, -1000, 1000, 1000);
106 'altitude' => ($Opt{'require'} =~ /a/) ?
1 : 0,
107 'time' => ($Opt{'require'} =~ /t/) ?
1 : 0
109 $Opt{'require'} =~ /[^at]/ && die("$0: Unknown flag in --require (-r) value\n");
111 $Opt{'debug'} && ($Debug = 1);
112 $Opt{'help'} && usage
(0);
113 $Opt{'version'} && print_version
();
115 if ($Opt{'pos1'} =~ /^($DIGIT+),($DIGIT+)$/) {
119 if ($Opt{'pos2'} =~ /^($DIGIT+),($DIGIT+)$/) {
134 if ($Opt{'inside'} && $Opt{'outside'}) {
135 die("$progname: Cannot mix the --inside and --outside options\n");
138 my $waypoint_file = "/home/sunny/gps/waypoints.gpx";
140 # To avoid printing out extra "/> at the start of svg output:
141 my $svg_start_thing = "";
143 length($Opt{'undefined'}) && ($Udef = $Opt{'undefined'});
144 # Kunne vært et eget script på grunn av at det gjør sine helt egne
145 # greier, men like greit å samle det på en plass.
146 # FIXME: Fjerner ikke første duplikatentryen.
147 # FIXME: Se om det går å få flytta den inn i print_entry() så man
148 # slipper å ha to gptrans_conv’er i pipen.
149 # FIXME: Legg inn alle formatene.
150 if ($Opt{'comment-out-dups'}) {
151 # Comment out areas without reception {{{
152 my ($start_date, $end_date, $found_dup) = ("", "", 0);
155 if (m
#^1 (\S+) (\S+) (\S+) (\S+) (\d\d)/(\d\d)/(\d\d\d\d) (\d\d):(\d\d):(\d\d)#) {
157 my ($lat_val, $lon_val, $Speed, $Unkn, $Month, $Day, $Year, $Hour, $Min, $Sec) =
158 ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
159 if (($lat_val eq $last_lat) && ($lon_val eq $last_lon)) {
160 unless ($found_dup) {
161 $start_date = "$Year$Month${Day}T$Hour$Min$Sec";
166 $end_date = "$Year$Month${Day}T$Hour$Min$Sec";
169 print("# $start_date-$end_date: CO: No signal \x7B\x7B\x7B\n");
173 print("# $start_date-$end_date: CO: No signal \x7D\x7D\x7D\n# move\n$_");
179 $last_lat = $lat_val;
180 $last_lon = $lon_val;
191 print("# $start_date-$end_date: CO: No signal \x7B\x7B\x7B\n");
195 print("# $start_date-$end_date: CO: No signal \x7D\x7D\x7D\n# move\n");
202 $Opt{'save-to-file'} eq "\n" && print_header
(*STDOUT
);
209 my $from_stdin = scalar(@ARGV) ?
0 : 1;
211 $from_stdin && push(@ARGV, "-");
213 for $curr_file (@ARGV) {
214 # Scan through stdin or specified files and send every GPS entry to
217 D
("Opening \"$curr_file\" for read");
218 if (open(CurrFP
, "<$curr_file")) {
223 'year' => '', 'month' => '', 'day' => '',
224 'hour' => '', 'min' => '', 'sec' => '',
225 'lat' => '', 'lon' => '',
232 if ($Opt{'save-to-file'} ne "\n") {
233 push(@first_lines, $_);
234 $_ =~ s/^# ?//; # Also read commented-out lines.
237 if (m
#^<(e?tp)\b.*?>(.*?)</(e?tp)>$#) {
238 # gpsml — The main storage format {{{
241 $Elem eq "etp" && ($Dat{'error'} = 1);
243 $Data =~ m
#<time>(.*?)</time># && ($Time = $1);
245 (\d\d\d\d
)-?
(\d\d
)-?
(\d\d
)T
(\d\d
):?
(\d\d
):?
([\d\
.]+?
)Z
247 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
248 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
253 $Data =~ m
#<lat>($DIGIT*?)</lat># && ($Dat{'lat'} = $1);
254 $Data =~ m
#<lon>($DIGIT*?)</lon># && ($Dat{'lon'} = $1);
255 $Data =~ m
#<ele>($DIGIT*?)</ele># && ($Dat{'ele'} = $1);
256 $Data =~ m
#<desc>(.*?)</desc># && ($Dat{'desc'} = xml_to_txt($1));
259 } elsif (m
#^<break\b.*?/>#) {
261 } elsif (m
#^<(desc|title|pause)\b.*?>(.*?)</(desc|title|pause)>#) {
263 $Dat{$1} = xml_to_txt
($2);
265 } elsif (/^<gpx\b/) {
267 $xml_data .= join("", <CurrFP
>);
268 if (!length($Opt{'output-format'})) {
269 $Opt{'output-format'} = "gpx";
270 print_header
(*STDOUT
);
272 read_xmlfile
($xml_data);
274 } elsif (/^# Pause: /) {
275 $Opt{'print-comments'} && print;
276 } elsif (/^# move$/) {
279 $Opt{'print-comments'} && print;
280 } elsif (m
#^(\d+)\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
281 # CSV format, epoch style {{{
282 my ($ep_time, $lon_val, $lat_val, $Alt) =
284 ($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
285 $Dat{'day'}, $Dat{'month'}, $Dat{'year'},
286 $Dat{'wday'}, $Dat{'yday'}) = gmtime($ep_time);
287 $Dat{'month'}++; # Urgh Ⅰ
288 $Dat{'year'} += 1900; # Urgh Ⅱ
291 } elsif (m
#^(\d\d\d\d)-?(\d\d)-?(\d\d)[T ](\d\d):?(\d\d):?(\d\d)Z?\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
292 # CSV format, human-readable date format {{{
293 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
294 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
295 $Dat{'lon'}, $Dat{'lat'}, $Dat{'ele'}) =
301 } elsif (/^Trackpoint\t/) {
302 # 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 {{{
305 # N60.41630 E5.31675\t
306 # 09.02.2006 20:24:37 (UTC)\t
314 $Orig =~ s/[\r\n]+$//;
315 my ($Marker_f, $Position_f, $Time_f, $Alt_f, $Depth_f,
316 $Leglength_f, $Legtime_f, $Legspeed_f, $Legcourse_f) =
318 # Nødløsning for å unngå at variabler blir
320 "\t\t\t\t\t\t\t\t\t\t"
323 "Position_f=\"$Position_f\" \x7B\x7B\x7B\n",
324 "Time_f=\"$Time_f\"\n",
325 "Alt_f=\"$Alt_f\"\n",
326 "Depth_f=\"$Depth_f\"\n",
327 "Leglength_f=\"$Leglength_f\"\n",
328 "Legtime_f=\"$Legtime_f\"\n",
329 "Legspeed_f=\"$Legspeed_f\"\n",
330 "Legcourse_f=\"$Legcourse_f\" \x7D\x7D\x7D\n",
335 $Legtime_hour, $Legtime_min, $Legtime_sec,
336 $Legspeed, $Legspeed_unit,
338 ) = ("", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
339 "", "", "", "", "", "", "", "");
340 ($Position_f =~ /^(N|S)([\d\.]+) (W|E)([\d\.]+)/) &&
341 ($NS = $1, $Dat{'lat'} = $2, $WE = $3, $Dat{'lon'} = $4);
342 ($Time_f =~ /^(\d+)\.(\d+)\.(\d+) (\d+):(\d+):(\d+) \((.+?)\)/) &&
343 ($Dat{'day'} = $1, $Dat{'month'} = $2, $Dat{'year'} = $3,
344 $Dat{'hour'} = $4, $Dat{'min'} = $5, $Dat{'sec'} = $6);
345 ($Alt_f =~ /^([\d+\.]+) (.*?)/) &&
346 ($Dat{'ele'} = $1, $Alt_unit = $2);
347 D
("ele = \"$Dat{'ele'}\"");
348 ($NS eq "S") && ($Dat{'lat'} = 0-$Dat{'lat'});
349 ($WE eq "W") && ($Dat{'lon'} = 0-$Dat{'lon'});
350 # MapSource in win xp writes YYYY, but YY in win98se.
351 (defined($Dat{'year'}) && $Dat{'year'} =~ /\d/ && $Dat{'year'} < 1900) && ($Dat{'year'} += 2000);
354 } elsif (m
#^T\t(\d\d)/(\d\d)/(\d\d\d\d) (\d\d):(\d\d):(\d\d)\t(.+)\xB0(.+)'(.+)"\t(.+)\xB0(.+)'(.+)"#) {
355 # T 09/01/2002 11:51:26 60°23'36.3" 5°19'35.9" {{{
356 my ($lat_d, $lat_m, $lat_s, $lon_d, $lon_m, $lon_s);
357 ($Dat{'month'}, $Dat{'day'}, $Dat{'year'},
358 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $lat_d,
359 $lat_m, $lat_s, $lon_d,
361 ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);
362 $Dat{'lat'} = sprintf("%.5f", 1*($lat_d+($lat_m/60)+($lat_s/3600)));
363 $Dat{'lon'} = sprintf("%.5f", $lon_d+($lon_m/60)+($lon_s/3600));
366 } elsif (m
#^1 (\S+) (\S+) (\S+) (\S+) (\d\d)/(\d\d)/(\d\d\d\d) (\d\d):(\d\d):(\d\d)#) {
367 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
368 ($Dat{'lat'}, $Dat{'lon'}, $Dat{'speed'},
370 $Dat{'month'}, $Dat{'day'}, $Dat{'year'},
371 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
372 ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
376 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
386 (\d\d
) # Latitude degree
387 (\d\d
) # Latitude minute
388 (\d\d\d
) # Latitude minute decimals
390 (\d\d\d
) # Longitude degree
391 (\d\d
) # Longitude minute
392 (\d\d\d
) # Longitude minute degree
399 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
400 $lon_degmin, $lon_mindec);
401 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'}, $Dat{'hour'},
402 $Dat{'min'}, $Dat{'sec'}, $NS, $lat_deg,
403 $lat_degmin, $lat_mindec, $EW,
404 $lon_deg, $lon_degmin, $lon_mindec,
405 $Dat{'accur'}, $Dat{'ele'}, $Dat{'unknown'}) =
406 ($2+2000, $3, $4, $5, $6, $7, $8, $9, $10, $11,
407 $12, $13, $14, $15, $16, $17, $18);
408 my $ep_time = timegm_nocheck
($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'}, $Dat{'day'}, $Dat{'month'}-1, $Dat{'year'});
409 $last_time = $ep_time;
410 my $tmp_lon = sprintf("%.5f", $lon_deg + $lon_degmin/60 + $lon_mindec/60000);
411 my $tmp_lat = sprintf("%.5f", $lat_deg + $lat_degmin/60 + $lat_mindec/60000);
412 $tmp_lon =~ s/\./$Des/;
413 $tmp_lat =~ s/\./$Des/;
414 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
415 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
416 $Dat{'lat'} = $tmp_lat;
417 $Dat{'lon'} = $tmp_lon;
420 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(__________________________________________)/) {
421 # @020721221336__________________________________________ {{{
422 my ($Alfa, $Year, $Month, $Day, $Hour, $Min, $Sec, $Rest) =
423 ( $1, $2+2000, $3, $4, $5, $6, $7, $8);
424 $Opt{'output-format'} eq "csv" && print("\n");
427 } elsif (/^xmaplog /) {
428 ($Opt{'output-format'} eq "csv") && ($Opt{'save-to-file'} eq "\n") && print("\n");
430 ($Opt{'output-format'} eq "csv") && ($Opt{'save-to-file'} eq "\n") && print("\n");
432 if ($Opt{'print-comments'}) {
436 $Opt{'verbose'} && warn("Line $.: Unknown: \"$_\"\n");
441 warn("$progname: $curr_file: Cannot open file for read: $!\n");
446 print_footer
(*STDOUT
);
452 D
("Inn i read_xmlfile()");
455 # print(Dumper($Txt->{trk}->{trkseg}->{trkpt}->[0]->{time}));
459 for (my $a = 0; $Loop; $a++) {
461 my $Date = $Txt->{trk
}->{trkseg
}->{trkpt
}->[$a]->{time};
462 $Dat{'lat'} = $Txt->{trk
}->{trkseg
}->{trkpt
}->[$a]->{lat
};
463 $Dat{'lon'} = $Txt->{trk
}->{trkseg
}->{trkpt
}->[$a]->{lon
};
464 $Dat{'ele'} = $Txt->{trk
}->{trkseg
}->{trkpt
}->[$a]->{ele
};
465 if ($Date =~ m
#(\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):?(\d\d):?(\d\d)\.?(\d*?)Z#) {
466 ($Dat{'year'}, $Dat{'mon'}, $Dat{'day'},
467 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $Dat{'secfrac'}) =
472 # print($Txt->{trk}->{trkseg}->{trkpt}->{time}->[$a]);
474 # FIXME: The sequential stuff here is probably bad, but easy.
475 # $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
476 # $Txt =~ s#(<gps\b.*?>.*?</gps>)#print_xml_gps($1)#gse;
484 D
("print_xml_gps(\"$Orig\")\n");
487 <trk
\b(.*?
)>(.*?
)</trk
>
493 <trkseg
\b(.*?
)>(.*?
)</trkseg
>
499 <trkpt
\b(.*?
)>(.*?
)</trkpt
>
502 my ($attr_trkpt, $el_trkpt) =
504 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon'} = $1);
505 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat'} = $1);
506 ($el_trkpt =~ m
#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele'} = $1);
507 if ($el_trkpt =~ m
#<time>(\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):?(\d\d):?(\d\d)\.?(\d*?)Z</time>#) {
508 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
509 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $Dat{'secfrac'}) =
510 ($1, $2, $3, $4, $5, $6, $7);
526 D
("print_xml_gps(\"$Orig\")\n");
527 if ($Str =~ m
#<date>(\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):?(\d\d):?(\d\d)\.?(\d*?)Z</date>#) {
528 ($Dat{'year'}, $Dat{'mon'}, $Dat{'day'}, $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $Dat{'secfrac'}) =
529 ( $1, $2, $3, $4, $5, $6, $7);
531 if ($Str =~ m
#<pos>(.*?)</pos>#s) {
533 ($Txt =~ m
#<x\b.*?>(.*?)</x>#) && ($Dat{'lon'} = $1);
534 ($Txt =~ m
#<y\b.*?>(.*?)</y>#) && ($Dat{'lat'} = $1);
535 ($Txt =~ m
#<z\b.*?>(.*?)</z>#) && ($Dat{'ele'} = $1);
537 defined($Dat{'lon'}) || ($Dat{'lon'} = "");
538 defined($Dat{'lat'}) || ($Dat{'lat'} = "");
539 defined($Dat{'ele'}) || ($Dat{'ele'} = "");
546 local *OutFP
= shift;
547 if ($Opt{'output-format'} eq "gpsml") {
549 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
553 } elsif ($Opt{'output-format'} eq "gpstrans") {
554 print(OutFP
"Format: DMS UTC Offset: 0.00 hrs Datum[100]: WGS 84\n");
555 } elsif ($Opt{'output-format'} eq "gpx") {
557 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
560 "$Spc$Spc$Spc$Spc<trkseg>\n",
562 } elsif ($Opt{'output-format'} eq "ps") {
563 print(OutFP ps_header
(532, 6034, 533, 6040));
565 } elsif ($Opt{'output-format'} eq "xml") {
567 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
570 } elsif ($Opt{'output-format'} eq "svg") {
572 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
573 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
574 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
575 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
576 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
577 "$Spc$Spc<title></title>\n",
578 "$Spc$Spc<desc></desc>\n",
586 local *OutFP
= shift;
587 if ($Opt{'output-format'} eq "gpsml") {
592 } elsif ($Opt{'output-format'} eq "gpx") {
594 "$Spc$Spc$Spc$Spc</trkseg>\n",
598 } elsif ($Opt{'output-format'} eq "poscount") {
599 while (my ($l_name, $l_val) = each %Poscount) {
600 $l_name =~ /^(.+?),(.+?)$/ && print(OutFP
"$1\t$2\t$l_val\n");
602 } elsif ($Opt{'output-format'} eq "ps") {
608 } elsif ($Opt{'output-format'} eq "svg") {
609 print(OutFP
"\"/>\n</svg>\n");
610 } elsif ($Opt{'output-format'} eq "xml") {
611 print(OutFP
"</gpslog>\n");
617 # Print a GPS entry with time, latitude, longitude and altitude in
621 my $print_time = length($Dat{'year'}) ?
1 : 0;
622 my $print_ele = length($Dat{'ele'}) ?
1 : 0;
623 my $print_desc = length($Dat{'desc'}) ?
1 : 0;
625 D
("print_entry(\"" . join("\", \"", @_) . "\");");
629 $Line .= sprintf("%s ", list_nearest_waypoints
($Dat{'lat'}, $Dat{'lon'}));
632 if ($Opt{'output-format'} eq "poscount") {
633 my ($Lat_str, $Lon_str) =
635 $Dat{'lon'} =~ /^(\d+\.\d\d)/ && ($Lon_str = $1);
636 $Dat{'lat'} =~ /^(\d+\.\d\d)/ && ($Lat_str = $1);
637 my $Name = "${Lon_str},${Lat_str}";
638 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
644 $ep_time = timegm_nocheck
($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'}, $Dat{'day'}, $Dat{'month'} - 1, $Dat{'year'});
645 $Dat{'year'} = sprintf("%04u", $Dat{'year'});
646 $Dat{'month'} = sprintf("%02u", $Dat{'month'});
647 $Dat{'day'} = sprintf("%02u", $Dat{'day'});
648 $Dat{'hour'} = sprintf("%02u", $Dat{'hour'});
649 $Dat{'min'} = sprintf("%02u", $Dat{'min'});
650 $Dat{'sec'} = sprintf("%02u", $Dat{'sec'});
651 if ($Opt{'chronology'}) {
652 if ($last_time > $ep_time) {
654 "%s: \"%sZ\": Next date is %s in the past (%sZ)\n",
655 $progname, sec_to_string
($last_time, "T"),
656 sec_to_readable
($last_time-$ep_time),
657 sec_to_string
($ep_time, "T")
659 # FIXME: Make --fix work with gpx and xml.
660 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^(gpx|xml)$/)) {
661 ($Line .= "# error ");
666 $Req{'time'} && return;
676 if ($Opt{'save-to-file'} ne "\n") {
678 my $base_name = "$Dat{'year'}$Dat{'month'}$Dat{'day'}T$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z$Opt{'save-to-file'}";
679 my $file_name = $base_name;
681 for (my $a = 1; (-e
$file_name) && ($a < 1000); $a++) {
682 $file_name = "$base_name.dup_$a";
685 die("$progname: $base_name: File already exists, and ran " .
686 "out of attempts to create unique file name\n");
688 if ($Opt{'verbose'}) {
689 warn("$progname: $base_name: File already exists, using " .
690 "unique name \"$file_name\" instead\n");
693 if (open(ToFP
, ">", $file_name)) {
695 print(ToFP
($from_stdin ?
@first_lines : ()), (length($xml_data) ?
$xml_data : <>)) ||
696 die("$progname: $file_name: Cannot write to file: $!\n");
699 if ($Opt{'output-format'} eq "gpsml") {
700 printf("<include>%s</include>\n",
701 txt_to_xml
($file_name));
702 } elsif ($Opt{'output-format'} eq "gpx") {
703 printf("<!-- Saved unconverted data to \"%s\" -->\n",
704 txt_to_xml
($file_name));
706 print("$progname: Saved unconverted data to \"$file_name\"\n");
710 die("$progname: $file_name: Cannot create file: $!\n");
717 ($Req{'altitude'} && !$print_ele) && return;
719 if ($Opt{'inside'} || $Opt{'outside'}) {
721 ($Dat{'lat'} < $lat1) ||
722 ($Dat{'lat'} > $lat2) ||
723 ($Dat{'lon'} < $lon1) ||
724 ($Dat{'lon'} > $lon2)
726 $Opt{'inside'} && return;
728 $Opt{'outside'} && return;
732 if ($Opt{'output-format'} eq "ps") {
736 if ($Opt{'skip-dups'} && ($Dat{'lon'} eq $last_lon) && ($Dat{'lat'} eq $last_lat) && ($Dat{'ele'} eq $last_ele)) {
745 $in_dupskip && ($Line .= $last_line);
749 if ($Opt{'create-breaks'} && $ep_time-$last_time > $PAUSE_LIMIT && $last_time) {
750 $pause_len = $ep_time-$last_time;
751 D
("pause_len set to '$pause_len'");
755 if ($Opt{'output-format'} eq "gpsml") {
756 $Line .= sprintf("<pause>%s</pause>\n",
757 sec_to_readable
($ep_time-$last_time));
758 } elsif ($Opt{'output-format'} eq "csv") {
759 $Line .= sprintf("# Pause: %s\n# move\n",
760 sec_to_readable
($ep_time-$last_time));
765 # Valid data was found, send to stdout {{{
766 unless ($first_time) {
767 $first_time = $ep_time;
769 if ($Opt{'double-y-scale'}) {
772 if ($Opt{'output-format'} eq "gpsml") {
773 if ($Dat{'type'} eq "tp") {
774 my $Elem = $Dat{'error'} ?
"etp" : "tp";
778 ?
"<time>$Dat{'year'}-$Dat{'month'}-$Dat{'day'}T" .
779 "$Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}Z</time> "
781 (length($Dat{'lat'}))
782 ?
"<lat>" . $Dat{'lat'}*1.0 . "</lat> "
784 (length($Dat{'lon'}))
785 ?
"<lon>" . $Dat{'lon'}*1.0 . "</lon> "
788 ?
"<ele>" . $Dat{'ele'}*1.0 . "</ele> "
791 ?
sprintf("<desc>%s</desc> ",
792 txt_to_xml
($Dat{'desc'}))
796 } elsif ($Dat{'type'} =~ /^(pause|desc|title)$/) {
797 $Line .= sprintf("<%s>%s</%s>\n",
799 txt_to_xml
($Dat{$1}),
802 } elsif ($Opt{'output-format'} eq "xgraph") {
803 $pause_len && ($Line .= "move ");
804 ($Line .= "$Dat{'lon'} $Dat{'lat'}\n");
805 } elsif($Opt{'output-format'} eq "gpstrans") {
806 my ($gpt_lat, $gpt_lon) = (ddd_to_dms
($Dat{'lat'}), ddd_to_dms
($Dat{'lon'}));
808 $Line .= "T\t$Dat{'month'}/$Dat{'day'}/$Dat{'year'} $Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}\t$gpt_lat\t$gpt_lon\n";
810 $Line .= "T\t00/00/00 00:00:00\t$gpt_lat\t$gpt_lon\n";
812 } elsif($Opt{'output-format'} eq "gpx") {
814 "$Spc$Spc$Spc$Spc$Spc$Spc<trkpt lat=\"$Dat{'lat'}\" lon=\"$Dat{'lon'}\">$Spc",
816 ?
"<time>$Dat{'year'}-$Dat{'month'}-$Dat{'day'}T$Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}Z</time>$Spc"
819 ?
"<ele>$Dat{'ele'}</ele>$Spc"
823 } elsif ($Opt{'output-format'} eq "clean") {
824 $pause_len && ($Line .= "\n");
825 ($Line .= "$Dat{'lon'}\t$Dat{'lat'}" . ($print_ele ?
"\t$Dat{'ele'}" : "") . "\n");
826 } elsif ($Opt{'output-format'} eq "ps") {
827 $Line .= ($pause_len ?
"f\n$Dat{'lon'} $Dat{'lat'} m\n" : "$Dat{'lon'} $Dat{'lat'} l\n");
828 } elsif ($Opt{'output-format'} eq "svg") {
830 ($last_lon == 1000) || $pause_len
832 "$svg_start_thing<path\n",
833 " stroke=\"blue\"\n",
834 " stroke-width=\"0.001\"\n",
837 "M $Dat{'lon'} $Dat{'lat'}\n",
839 : "L $Dat{'lon'} $Dat{'lat'}\n"
841 } elsif ($Opt{'output-format'} eq "xml") {
845 ?
"<date>$Dat{'year'}-$Dat{'month'}-$Dat{'day'}T$Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}Z</date>$Spc"
848 (length($Dat{'lon'})) ?
"<x>$Dat{'lon'}</x>$Spc" : "",
849 (length($Dat{'lat'})) ?
"<y>$Dat{'lat'}</y>$Spc" : "",
850 ($print_ele) ?
"<z>$Dat{'ele'}</z>$Spc" : "",
854 } elsif ($Opt{'output-format'} eq "ygraph") {
855 my $Time = $print_time ?
($ep_time - $first_time) * 1 : 0;
856 $Line .= "\"Time = $Time.0\n$Dat{'lon'} $Dat{'lat'}\n\n";
857 } elsif ($Opt{'output-format'} eq "csv") {
859 $Dat{'lon'} =~ s/\./$Des/;
860 $Dat{'lat'} =~ s/\./$Des/;
861 # $do_print || print("skipping ");
867 ?
"$Dat{'year'}$Dat{'month'}$Dat{'day'}T$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z"
868 : "$Dat{'year'}-$Dat{'month'}-$Dat{'day'} $Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}"
872 $print_ele ?
$Dat{'ele'} : "", # Elevation
877 die("$progname: \"$Opt{'output-format'}\": Unknown output format\n");
882 if (!$last_time && $Opt{'output-format'} eq "ps") {
883 $Line .= "$Dat{'lon'} $Dat{'lat'} m\n";
888 if ($Opt{'output-format'} eq "gpsml") {
889 $Line = "<break/>\n$Line";
891 (!$pause_len && ($Opt{'output-format'} eq "xgraph")) && ($Line .= "move $Line");
892 ($Opt{'output-format'} eq "clean") && ($Line .= "\n");
893 if ($Opt{'output-format'} eq "gpx") {
894 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n$Spc$Spc$Spc$Spc<trkseg>\n";
900 $last_time = $ep_time;
901 $last_lon = $Dat{'lon'};
902 $last_lat = $Dat{'lat'};
903 $last_ele = $Dat{'ele'};
904 $last_line = $data_line;
905 $svg_start_thing = "\"/>\n";
909 sub list_nearest_waypoints
{
911 my ($Lat, $Lon, $Count) = @_;
912 # FIXME: Incredible unfinished and kludgy.
913 if (open(WaypFP
, "gpsbabel -i gpx -f $waypoint_file -x radius,lat=$Lat,lon=$Lon,distance=1000 -o gpx -F - |")) {
914 my $Str = join("", <WaypFP
>);
916 ^.*?
<wpt\s
.*?
>.*?
<name
>(.+?
)</name>.*?</wpt
>.*?
917 .*?
<wpt\s
.*?
>.*?
<name
>(.+?
)</name>.*?</wpt
>.*?
918 .*?
<wpt\s
.*?
>.*?
<name
>(.+?
)</name>.*?</wpt
>.*$
924 die("$progname: Cannot open gpsbabel pipe: $!\n");
930 # Convert seconds since 1970 to "yyyy-mm-dd hh:mm:ss" with optional
933 my ($Seconds, $Sep) = @_;
934 defined($Sep) || ($Sep = " ");
935 my @TA = gmtime($Seconds);
936 my($DateString) = sprintf("%04u-%02u-%02u%s%02u:%02u:%02u", $TA[5]+1900, $TA[4]+1, $TA[3], $Sep, $TA[2], $TA[1], $TA[0]);
941 sub sec_to_readable
{
942 # Convert seconds since 1970 to human-readable format (d:hh:mm:ss)
945 D
("sec_to_readable(\"$secs\")\n");
946 my ($Day, $Hour, $Min, $Sec) =
949 $Day = int($secs/86400);
950 $secs -= $Day * 86400;
952 $Hour = int($secs/3600);
953 $secs -= $Hour * 3600;
955 $Min = int($secs/60);
960 return(sprintf("%u:%02u:%02u:%02u", $Day, $Hour, $Min, $Sec));
965 # Send a Postscript header to stdout {{{
966 my ($bl_lon, $bl_lat, $br_lon, $br_lat) = @_;
967 my $Date = sec_to_string
(time);
969 "%!PS-Adobe-3.0 EPSF-3.0\n",
970 "%%Creator: $rcs_id\n",
972 "%%CreationDate: $Date\n",
973 "%%BoundingBox: $bl_lon $bl_lat $br_lon $br_lat\n",
974 "%%DocumentData: Clean7Bit\n",
977 "/bd { bind def } bind def\n",
978 "/incompound false def\n",
979 "/m { moveto } bd\n",
980 "/l { lineto } bd\n",
981 "/c { curveto } bd\n",
982 "/F { incompound not {fill} if } bd\n",
983 "/f { closepath F } bd\n",
984 "/S { stroke } bd\n",
985 "/*u { /incompound true def } bd\n",
986 "/*U { /incompound false def f} bd\n",
987 "/k { setcmykcolor } bd\n",
997 # Convert floating-point degrees into D°M'S.S" (ISO-8859-1).
998 # Necessary for import into GPSman. Based on toDMS() from
999 # gpstrans-0.39 to ensure compatibility.
1003 my ($Hour, $Min, $Sec) =
1012 $ddd = ($ddd - $Hour) * 60.0;
1014 $Sec = ($ddd - $Min) * 60.0;
1027 D
("Neg = $Neg , D = $Hour , M = $Min , S = $Sec\n");
1028 $Retval = sprintf("%s%.0f\xB0%02.0f'%04.1f\"", $Neg ?
"-" : "", $Hour, $Min, $Sec);
1034 # Convert plain text to XML {{{
1036 $Txt =~ s/&/&/gs;
1037 $Txt =~ s/</</gs;
1038 $Txt =~ s/>/>/gs;
1044 # Convert XML data to plain text {{{
1046 $Txt =~ s/</</gs;
1047 $Txt =~ s/>/>/gs;
1048 $Txt =~ s/&/&/gs;
1054 # Print program version {{{
1061 # Send the help message to stdout {{{
1068 Converts between various GPS formats.
1070 Usage: $progname [options] [file [files [...]]]
1071 $progname -S [file [files [...]]]
1072 $progname -u [file [files [...]]]
1076 -c, --comment-out-dups
1077 Use comma instead of period as decimal point (For Gnumeric etc).
1078 -C, --print-comments
1079 Print existing comment lines (starting with "#") and prefix unknown
1082 Check for broken chronology, warn about entries with an old
1085 Skip duplicated coordinates, only print first and last.
1087 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1089 Comment out entries which is obviously wrong. Use together with
1090 --chronology to fix those kind of errors. Does not work with GPX or
1095 Print only trackpoints inside a rectangle specified by --pos1 and
1098 Use x as undefined value. Default: "$Udef".
1100 Add names of the three closest waypoints to the trackpoint.
1101 Unfinished and experimental, needs gpsbabel.
1102 -o x, --output-format x
1103 Use output format x:
1109 poscount (Experimental)
1116 Print only trackpoints outside a rectangle specified by --pos1 and
1119 Specifies one corner where x is in "lat,lon" format (decimal
1120 degrees, negative for west or south) of area rectangle used by the
1121 --inside and --outside options.
1123 Specify requirements for trackpoints to be written. x is a string
1124 with the following flags:
1126 Print only waypoints which have an altitude.
1128 Print only waypoints which have a timestamp.
1130 Use short date format.
1131 -S x, --save-to-file x
1132 Save the unconverted data to a file with a filename starting with
1133 the timestamp of the first trackpoint. The parameter string x is
1134 added at the end of the filename. For the time being this option
1135 will ignore all other options. Note: If several files are specified
1136 on the command line, all data will be saved into only one file. This
1137 behaviour may change in the future.
1139 Create breaks in track between points with a difference more than
1140 $PAUSE_LIMIT seconds.
1141 -u, --comment-out-dups
1142 Comment out following data with identical position values, only
1145 Verbose, warn about unknown lines.
1147 Print version information.
1148 -w, --strip-whitespace
1149 Strip all unnecessary whitespace.
1150 -y, --double-y-scale
1151 Double Y scale (latitude) to get it right in gnuplot.
1153 Print debugging messages.
1161 # Print a debugging message {{{
1163 my @call_info = caller;
1164 chomp(my $Txt = shift);
1165 my $File = $call_info[1];
1167 $File =~ s
#^.*/(.*?)$#$1#;
1168 print(STDERR
"$File:$call_info[2] $$ $Txt\n");
1175 # Plain Old Documentation (POD) {{{
1189 B<gptrans_conv> [options] [file [files [...]]]
1191 B<gptrans_conv> -S [options] [file [files [...]]]
1193 B<gptrans_conv> -u [options] [file [files [...]]]
1197 Converts between various GPS formats.
1203 =item B<-c>, B<--comment-out-dups>
1205 Use comma instead of period as decimal point (For Gnumeric etc).
1207 =item B<-C>, B<--print-comments>
1209 Print existing comment lines (starting with "#") and prefix unknown
1212 =item B<--chronology>
1214 Check for broken chronology, warn about entries with an old timestamp.
1216 =item B<-d>, B<--skip-dups>
1218 Skip duplicated coordinates, only print first and last.
1220 =item B<-e>, B<--epoch>
1222 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1226 Comment out entries which is obviously wrong. Use together with
1227 --chronology to fix those kind of errors. Does not work with GPX or XML
1230 =item B<-h>, B<--help>
1236 Print only trackpoints inside a rectangle specified by --pos1 and
1241 Add names of the three closest waypoints to the trackpoint. Unfinished
1242 and experimental, needs gpsbabel.
1244 =item B<-n x>, B<--undefined x>
1246 Use x as undefined value.
1248 =item B<-o x>, B<--output-format x>
1250 Use output format x:
1260 =item gpsml (Default)
1262 This is the format which is meant to be used when storing the track
1264 It is line-based XML which makes it easy to edit and grep. Probably not
1269 =item gpx (Not complete)
1271 =item poscount (Experimental)
1273 Creates a 3D plot where areas with many trackpoints are higher than
1274 areas with less track points.
1276 =item ps (Unfinished)
1278 =item svg (Unfinished)
1294 Print only trackpoints outside a rectangle specified by --pos1 and
1297 =item B<--pos1 x>, B<--pos2 x>
1299 Specifies corners of an area rectangle used by the --inside and
1300 --outside options. The x value is in "lat,lon" format (decimal degrees,
1301 negative for west or south) .
1303 =item B<-r x>, B<--require x>
1305 Specify requirements for trackpoints to be written. x is a string with
1306 the following flags:
1316 =item Print only waypoints which have an altitude.
1328 =item Print only waypoints which have a timestamp.
1338 =item B<-s>, B<--short-date>
1340 Use short date format.
1342 =item B<-S x>, B<--save-to-file x>
1344 Save the unconverted data to a file with a filename starting with the
1345 timestamp of the first trackpoint. The parameter string x is added at
1346 the end of the filename. For the time being this option will ignore all
1349 Note: If several files are specified on the command line, all data will
1350 be saved into only one file. This behaviour may change in the future.
1352 =item B<-t>, B<--create-breaks>
1354 Create breaks in track between points with a difference more than the
1355 number of seconds specified by the C<$PAUSE_LIMIT> variable.
1357 =item B<-u>, B<--comment-out-dups>
1359 Comment out following data with identical position values, only print
1362 =item B<-v>, B<--verbose>
1364 Verbose, warn about unknown lines.
1366 =item B<-w>, B<--strip-whitespace>
1368 Strip all unnecessary whitespace.
1370 =item B<-x>, B<--xml>
1374 =item B<-y>, B<--double-y-scale>
1376 Double Y scale (latitude) to get it right in gnuplot.
1378 =item B<-h>, B<--help>
1380 Print a brief help summary.
1384 Print version information.
1388 Print debugging messages.
1394 Pretty incomplete in some areas. Some of the source formats are
1395 undocumented and thus incomplete. Some functionality is not working
1396 properly, for example the Postscript output.
1400 Made by Øyvind A. Holm S<E<lt>sunny@sunbase.orgE<gt>>.
1404 Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1405 This is free software; see the file F<COPYING> for legalese stuff.
1409 This program is free software; you can redistribute it and/or modify it
1410 under the terms of the GNU General Public License as published by the
1411 Free Software Foundation; either version 2 of the License, or (at your
1412 option) any later version.
1414 This program is distributed in the hope that it will be useful, but
1415 WITHOUT ANY WARRANTY; without even the implied warranty of
1416 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1417 See the GNU General Public License for more details.
1419 You should have received a copy of the GNU General Public License along
1420 with this program; if not, write to the Free Software Foundation, Inc.,
1421 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1431 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :