]> git.mxchange.org Git - flightgear.git/blob - scripts/perl/dafif/dafift2ils.pl
Remove possilbe trailing "/" from runway numbers in FAA data.
[flightgear.git] / scripts / perl / dafif / dafift2ils.pl
1 #!/usr/bin/perl
2
3 ########################################################################
4 # Convert DAFIFT ARPT/ILS.TXT to FlightGear format.
5 ########################################################################
6
7 use strict;
8
9 my($faa_ils_file) = shift(@ARGV);
10 my($dafift_arpt_file) = shift(@ARGV);
11 my($dafift_ils_file) = shift(@ARGV);
12 my($fgfs_ils_file) = shift(@ARGV);
13 my($output_file) = shift(@ARGV);
14
15 die "Usage: $0 " .
16     "<faa_ils_file> <dafift_arpt_file> <dafift_ils_file> <fgfs_ils_file> <output_file>\n"
17     if !defined($faa_ils_file) || !defined($dafift_arpt_file)
18        || !defined($dafift_ils_file) || !defined($fgfs_ils_file)
19        || !defined($output_file);
20
21 my( %CODES );
22 my( %CodesByICAO );
23 my( %ILS );
24 my( %AIRPORTS );
25
26
27 &load_dafift( $dafift_arpt_file, $dafift_ils_file );
28 &load_faa( $faa_ils_file );
29 &load_fgfs( $fgfs_ils_file );
30 &write_result( $output_file );
31
32 exit;
33
34
35 ########################################################################
36 # Process DAFIFT data
37 ########################################################################
38
39 sub load_dafift() {
40     my( $arpt_file ) = shift;
41     my( $ils_file ) = shift;
42
43     my( $record );
44
45     my( $id, $rwy, $type );
46     my( $has_dme, $has_gs, $has_loc, $has_im, $has_mm, $has_om );
47     my( $dme_lon, $dme_lat, $dme_elev, $dme_bias );
48     my( $gs_lon, $gs_lat, $gs_elev, $gs_angle );
49     my( $loc_type, $loc_lon, $loc_lat, $loc_elev, $loc_freq, $loc_hdg,
50         $loc_width, $loc_id );
51     my( $im_lon, $im_lat, $mm_lon, $mm_lat, $om_lon, $om_lat );
52
53     # load airport file so we can lookup ICAO from internal ID
54
55     open( ARPT, "<$arpt_file" ) || die "Cannot open DAFIFT: $arpt_file\n";
56
57     <ARPT>;                          # skip header line
58
59     while ( <ARPT> ) {
60         chomp;
61         my(@F) = split(/\t/);
62         my($icao) = $F[3];
63         if ( length($icao) < 3 ) {
64             if ( length( $F[4] ) >= 3 ) {
65                 $icao = $F[4];
66             } else {
67                 $icao = "[none]";
68             }
69         }
70         $CODES{$F[0]} = $icao;
71         $CodesByICAO{$icao} = 1;
72         # print "$F[0] - $icao\n";
73     }
74
75     # Load the DAFIFT ils file
76
77     my( $last_id, $last_rwy ) = ("", "");
78
79     open( DAFIFT_ILS, "<$ils_file" ) || die "Cannot open DAFIFT: $ils_file\n";
80
81     <DAFIFT_ILS>;                   # skip header line
82
83     while ( <DAFIFT_ILS> ) {
84         chomp;
85         my @F = split(/\t/);
86         $id = $F[0];
87         $rwy = $F[1];
88
89         if ( $last_id ne "" && ($last_id ne $id || $last_rwy ne $rwy) ) {
90             # just hist the start of the next record, dump the current data
91
92             if ( ! $has_gs ) {
93                 ( $gs_elev, $gs_angle, $gs_lat, $gs_lon ) = ( 0, 0, 0, 0 );
94             }
95             if ( ! $has_dme ) {
96                 ( $dme_lat, $dme_lon ) = ( 0, 0 );
97             }
98             if ( ! $has_om ) {
99                 ( $om_lat, $om_lon ) = ( 0, 0 );
100             }
101             if ( ! $has_mm ) {
102                 ( $mm_lat, $mm_lon ) = ( 0, 0 );
103             }
104             if ( ! $has_im ) {
105                 ( $im_lat, $im_lon ) = ( 0, 0 );
106             }
107             if ( $ILS{$CODES{$last_id} . $last_rwy} eq "" ) {
108                 print "DAFIFT adding: $CODES{$last_id} - $last_rwy\n";
109                 &safe_add_record( $CODES{$last_id}, $last_rwy, "ILS", 
110                                   $loc_freq, $loc_id, $loc_hdg, $loc_lat,
111                                   $loc_lon, $gs_elev, $gs_angle, $gs_lat,
112                                   $gs_lon, $dme_lat, $dme_lon, $om_lat,
113                                   $om_lon, $mm_lat, $mm_lon, $im_lat,
114                                   $im_lon );
115             }
116
117             $has_dme = 0;
118             $has_gs = 0;
119             $has_loc = 0;
120             $has_im = 0;
121             $has_mm = 0;
122             $has_om = 0;
123         }
124
125         $type = $F[2];
126         if ( $type eq "D" ) {
127             # DME entry
128             $has_dme = 1;
129             $dme_lon = make_dcoord( $F[16] );
130             $dme_lat = make_dcoord( $F[14] );
131             $dme_elev = $F[10];
132             if ( $dme_elev !~ m/\d/ ) {
133                 $dme_elev = "";
134             } else {
135                 $dme_elev += 0;
136             }
137             $dme_bias = $F[27];
138             # print "$id DME $dme_lon $dme_lat $dme_elev $dme_bias\n";
139         } elsif ( $type eq "G" ) {
140             # GlideSlope entry
141             $has_gs = 1;
142             $gs_lon = make_dcoord( $F[16] );
143             $gs_lat = make_dcoord( $F[14] );
144             $gs_elev = $F[10];
145             if ( $gs_elev !~ m/\d/ ) {
146                 $gs_elev = "";
147             } else {
148                 $gs_elev += 0;
149             }
150             $gs_angle = $F[7];
151             # print "$id GS $gs_lon $gs_lat $gs_elev $gs_angle\n";
152         } elsif ( $type eq "Z" ) {
153             # Localizer entry
154             $has_loc = 1;
155             $loc_lon = make_dcoord( $F[16] );
156             $loc_lat = make_dcoord( $F[14] );
157             $loc_elev = $F[10];
158             if ( $loc_elev !~ m/\d/ ) {
159                 $loc_elev = "";
160             } else {
161                 $loc_elev += 0;
162             }
163             ($loc_freq) = $F[5] =~ m/(\d\d\d\d\d\d)/;
164             $loc_freq /= 1000.0;
165             my( $magvar ) = make_dmagvar( $F[22] );
166             # print "mag var = $F[22] (" . $magvar . ")\n";
167             $loc_hdg = $F[24] + make_dmagvar( $F[22] );
168             $loc_width = $F[25];
169             $loc_id = $F[18];
170             if ( length( $loc_id ) >= 4 ) {
171                 $loc_id =~ s/^I//;
172             }
173             # print "$id LOC $loc_lon $loc_lat $loc_elev $loc_freq $loc_hdg $loc_width\n";
174         } elsif ( $type eq "I" ) {
175             # Inner marker entry
176             $has_im = 1;
177             $im_lon = make_dcoord( $F[16] );
178             $im_lat = make_dcoord( $F[14] );
179             # print "$id IM $im_lon $im_lat\n";
180         } elsif ( $type eq "M" ) {
181             # Middle marker entry
182             $has_mm = 1;
183             $mm_lon = make_dcoord( $F[16] );
184             $mm_lat = make_dcoord( $F[14] );
185             # print "$id MM $mm_lon $mm_lat\n";
186         } elsif ( $type eq "O" ) {
187             # Outer marker entry
188             $has_om = 1;
189             $om_lon = make_dcoord( $F[16] );
190             $om_lat = make_dcoord( $F[14] );
191             # print "$id OM $om_lon $om_lat\n";
192         }
193
194         $last_id = $id;
195         $last_rwy = $rwy;
196         # printf("%-5s %10.6f %11.6f\n",  $F[0], $F[14], $F[16]);
197     }
198
199     if ( ! $has_gs ) {
200         ( $gs_elev, $gs_angle, $gs_lat, $gs_lon ) = ( 0, 0, 0, 0 );
201     }
202     if ( ! $has_dme ) {
203         ( $dme_lat, $dme_lon ) = ( 0, 0 );
204     }
205     if ( ! $has_om ) {
206         ( $om_lat, $om_lon ) = ( 0, 0 );
207     }
208     if ( ! $has_mm ) {
209         ( $mm_lat, $mm_lon ) = ( 0, 0 );
210     }
211     if ( ! $has_im ) {
212         ( $im_lat, $im_lon ) = ( 0, 0 );
213     }
214     if ( $ILS{$CODES{$last_id} . $last_rwy} eq "" ) {
215         print "DAFIFT adding (last): $CODES{$last_id} - $last_rwy\n";
216         &safe_add_record( $CODES{$last_id}, $last_rwy, "ILS", $loc_freq,
217                           $loc_id, $loc_hdg, $loc_lat, $loc_lon,
218                           $gs_elev, $gs_angle, $gs_lat, $gs_lon,
219                           $dme_lat, $dme_lon, $om_lat, $om_lon, $mm_lat,
220                           $mm_lon, $im_lat, $im_lon );
221     }
222 }
223
224
225 ########################################################################
226 # Process FAA data
227 ########################################################################
228
229 sub load_faa() {
230     my( $file ) = shift;
231
232     open( FAA_ILS, "<$file" ) || die "Cannot open FAA data: $file\n";
233
234     <FAA_ILS>;                          # skip header line
235
236     while ( <FAA_ILS> ) {
237         chomp;
238
239         my ( $rec_type, $faa_id, $rwy, $type, $faa_date,
240              $faa_apt_name, $faa_city, $faa_st, $faa_state,
241              $faa_region, $id, $faa_len, $faa_wid, $faa_cat,
242              $faa_owner, $faa_operator, $faa_bearing, $faa_magvar,
243              $loc_type, $loc_id, $loc_freq, $faa_loc_latd,
244              $faa_loc_lats, $faa_loc_lond, $faa_loc_lons, $loc_width,
245              $faa_stop_dist, $faa_app_dist, $faa_gs_type, $gs_angle,
246              $faa_gs_freq, $faa_gs_latd, $faa_gs_lats, $faa_gs_lond,
247              $faa_gs_lons, $faa_gs_dist, $gs_elev, $faa_im_type,
248              $faa_im_latd, $faa_im_lats, $faa_im_lond, $faa_im_lons,
249              $faa_im_dist, $faa_mm_type, $faa_mm_id, $faa_mm_name,
250              $faa_mm_freq, $faa_mm_latd, $faa_mm_lats, $faa_mm_lond,
251              $faa_mm_lons, $faa_mm_dist, $faa_om_type, $faa_om_id,
252              $faa_om_name, $faa_om_freq, $faa_om_latd, $faa_om_lats,
253              $faa_om_lond, $faa_om_lons, $faa_om_dist,
254              $faa_om_backcourse, $faa_dme_channel, $faa_dme_latd,
255              $faa_dme_lats, $faa_dme_lond, $faa_dme_lons, $faa_dme_app_dist,
256              $faa_dme_stop_dist, $blank)
257             = $_ =~
258             m/^(.{4})(.{11})(.{3})(.{10})(.{10})(.{42})(.{26})(.{2})(.{20})(.{3})(.{4})(.{5})(.{4})(.{9})(.{50})(.{50})(.{3})(.{3})(.{15})(.{5})(.{6})(.{14})(.{11})(.{14})(.{11})(.{5})(.{5})(.{6})(.{15})(.{4})(.{6})(.{14})(.{11})(.{14})(.{11})(.{6})(.{7})(.{15})(.{14})(.{11})(.{14})(.{11})(.{6})(.{15})(.{2})(.{5})(.{3})(.{14})(.{11})(.{14})(.{11})(.{6})(.{15})(.{2})(.{5})(.{3})(.{14})(.{11})(.{14})(.{11})(.{6})(.{9})(.{4})(.{14})(.{11})(.{14})(.{11})(.{6})(.{5})(.{34})/;
259
260         $id = &strip_ws( $id );
261         $rwy = &strip_ws( $rwy );
262         $rwy =~ s/\/$//;
263         $rwy =~ s/\/$//;
264         $loc_id =~ s/^I-//;
265         my( $loc_hdg ) = $faa_bearing + make_dmagvar($faa_magvar);
266         my( $loc_lat ) = make_dcoord($faa_loc_lats) / 3600.0;
267         my( $loc_lon ) = make_dcoord($faa_loc_lons) / 3600.0;
268         # print "$loc_lon $loc_lat $faa_loc_lons $faa_loc_lats\n";
269         my( $gs_lat ) = make_dcoord($faa_gs_lats) / 3600.0;
270         my( $gs_lon ) = make_dcoord($faa_gs_lons) / 3600.0;
271         my( $im_lat ) = make_dcoord($faa_im_lats) / 3600.0;
272         my( $im_lon ) = make_dcoord($faa_im_lons) / 3600.0;
273         my( $mm_lat ) = make_dcoord($faa_mm_lats) / 3600.0;
274         my( $mm_lon ) = make_dcoord($faa_mm_lons) / 3600.0;
275         my( $om_lat ) = make_dcoord($faa_om_lats) / 3600.0;
276         my( $om_lon ) = make_dcoord($faa_om_lons) / 3600.0;
277         my( $dme_lat ) = make_dcoord($faa_dme_lats) / 3600.0;
278         my( $dme_lon ) = make_dcoord($faa_dme_lons) / 3600.0;
279
280         # my( $key );
281         # print "$id - $rwy\n";
282         # $key = $id . $rwy;
283         # print "-> $key -> $ILS{$key}\n";
284         # $key = "K" . $id . $rwy;
285         # print "-> $key -> $ILS{$key}\n";
286
287         if ( $rec_type eq "ILS1" ) {
288             if ( length( $id ) < 4 ) {
289                 if ( $CodesByICAO{"K" . $id} ) {
290                     $id = "K" . $id;
291                 }
292             }
293             if ( $ILS{$id . $rwy} ne "" ) {
294                 print "FAA updating: $id - $rwy $type\n";
295                 &update_type( $id, $rwy, $type );
296             } else {
297                 print "FAA adding: $id - $rwy\n";
298                 &safe_add_record( $id, $rwy, $type, $loc_freq, $loc_id,
299                                   $loc_hdg, $loc_lat, $loc_lon, $gs_elev,
300                                   $gs_angle, $gs_lat, $gs_lon, $dme_lat,
301                                   $dme_lon, $om_lat, $om_lon, $mm_lat,
302                                   $mm_lon, $im_lat, $im_lon );
303             }
304         }
305     }
306 }
307
308
309 ########################################################################
310 # Process FlightGear ILS data
311 ########################################################################
312
313 sub load_fgfs() {
314     my( $ils_file ) = shift;
315
316     open( FGILS, "zcat $ils_file|" ) || die "Cannot open FGFS: $ils_file\n";
317
318     <FGILS>;                          # skip header line
319
320     while ( <FGILS> ) {
321         chomp;
322         if ( ! m/\[End\]/ && length($_) > 1 ) {
323             # print "$_\n";
324             my( $type_code, $type_name, $icao, $rwy, $loc_freq, $loc_id,
325                 $loc_hdg, $loc_lat, $loc_lon, $gs_elev, $gs_angle, $gs_lat,
326                 $gs_lon, $dme_lat, $dme_lon, $om_lat, $om_lon, $mm_lat, $mm_lon,
327                 $im_lat, $im_lon ) = split(/\s+/);
328             my( $code ) = $icao;
329             $code =~ s/^K//;
330             if ( $ILS{$icao . $rwy} ne "" ) {
331                 # skip approaches already in FAA or DAFIFT data
332             } elsif ( $AIRPORTS{$icao} != 1 && $AIRPORTS{$code} != 1 ) {
333                 # skip approaches if the FAA or DAFIFT data has any other
334                 # approach already for this airport.  (Avoids carrying over
335                 # extraneous data if an approach was deactivated or runway
336                 # numbers were changed.)
337             } elsif ( length( $icao ) < 4 || $icao =~ m/^K/ ) {
338                 # skip USA approaches not found in FAA or DAFIFT data
339             } else {
340                 print "FGFS adding: $icao $rwy\n";
341                 &safe_add_record( $icao, $rwy, $type_name, $loc_freq, $loc_id,
342                                   $loc_hdg, $loc_lat, $loc_lon, $gs_elev,
343                                   $gs_angle, $gs_lat, $gs_lon, $dme_lat,
344                                   $dme_lon, $om_lat, $om_lon, $mm_lat,
345                                   $mm_lon, $im_lat, $im_lon );
346             }
347         } else {
348             print "FGFS discarding: $_\n";
349         }
350     }
351 }
352
353
354 ########################################################################
355 # Write out the accumulated combined result
356 ########################################################################
357
358 sub write_result() {
359     my( $outfile ) = shift;
360
361     open( OUT, ">$outfile" )  || die "Cannot write to: $outfile\n";
362
363     # dump out the final results
364     print OUT "// FlightGear ILS data, generated from DAFIFT ARPT/ILS.TXT and FAA data\n";
365
366     my( $key );
367     foreach $key ( sort (keys %ILS) ) {
368         print OUT "$ILS{$key}\n";
369     }
370     print OUT "[End]\n";
371 }
372
373
374 ########################################################################
375 # Utility functions
376 ########################################################################
377
378
379 # add a record to the master list if it doesn't already exist
380
381 sub safe_add_record() {
382     my( $apt_id ) = shift;
383     my( $rwy ) = shift;
384     my( $type ) = shift;
385     my( $loc_freq ) = shift;
386     my( $loc_id ) = shift;
387     my( $loc_hdg ) = shift;
388     my( $loc_lat ) = shift;
389     my( $loc_lon ) = shift;
390     my( $gs_elev ) = shift;
391     my( $gs_angle ) = shift;
392     my( $gs_lat ) = shift;
393     my( $gs_lon ) = shift;
394     my( $dme_lat ) = shift;
395     my( $dme_lon ) = shift;
396     my( $om_lat ) = shift;
397     my( $om_lon ) = shift;
398     my( $mm_lat ) = shift;
399     my( $mm_lon ) = shift;
400     my( $im_lat ) = shift;
401     my( $im_lon ) = shift;
402
403     if ( $ILS{$apt_id . $rwy} eq "" ) {
404         # print "Safe adding (common): $apt_id - $rwy\n";
405         &update_record( $apt_id, $rwy, $type, $loc_freq, $loc_id,
406                         $loc_hdg, $loc_lat, $loc_lon, $gs_elev,
407                         $gs_angle, $gs_lat, $gs_lon, $dme_lat,
408                         $dme_lon, $om_lat, $om_lon, $mm_lat,
409                         $mm_lon, $im_lat, $im_lon );
410     }
411 }
412
413
414 # replace a record in the master list (or add it if it doesn't exist)
415
416 sub update_record() {
417     my( $apt_id ) = shift;
418     my( $rwy ) = shift;
419     my( $type ) = shift;
420     my( $loc_freq ) = shift;
421     my( $loc_id ) = shift;
422     my( $loc_hdg ) = shift;
423     my( $loc_lat ) = shift;
424     my( $loc_lon ) = shift;
425     my( $gs_elev ) = shift;
426     my( $gs_angle ) = shift;
427     my( $gs_lat ) = shift;
428     my( $gs_lon ) = shift;
429     my( $dme_lat ) = shift;
430     my( $dme_lon ) = shift;
431     my( $om_lat ) = shift;
432     my( $om_lon ) = shift;
433     my( $mm_lat ) = shift;
434     my( $mm_lon ) = shift;
435     my( $im_lat ) = shift;
436     my( $im_lon ) = shift;
437
438     my( $record );
439
440     # remap $type as needed
441     $type = &strip_ws( $type );
442     if ( $type eq "LOCALIZER" ) {
443         $type = "LOC";
444     } elsif ( $type eq "ILS/DME" ) {
445         $type = "ILS";
446     } elsif ( $type eq "SDF/DME" ) {
447         $type = "SDF";
448     } elsif ( $type eq "LOC/DME" ) {
449         $type = "ILS";
450     } elsif ( $type eq "LOC/GS" ) {
451         $type = "LOC";
452     } elsif ( $type eq "LDA/DME" ) {
453         $type = "LDA";
454     }
455
456     $record = sprintf( "%1s %-5s %-4s %-3s  %06.2f %-4s %06.2f %10.6f %11.6f ",
457                        substr( $type, 0, 1 ), $type, $apt_id, $rwy,
458                        $loc_freq, $loc_id, $loc_hdg, $loc_lat, $loc_lon );
459     $record .= sprintf( "%5d %5.2f %10.6f %11.6f ",
460                         $gs_elev, $gs_angle, $gs_lat, $gs_lon );
461     $record .= sprintf( "%10.6f %11.6f ", $dme_lat, $dme_lon );
462     $record .= sprintf( "%10.6f %11.6f ", $om_lat, $om_lon );
463     $record .= sprintf( "%10.6f %11.6f ", $mm_lat, $mm_lon );
464     $record .= sprintf( "%10.6f %11.6f ", $im_lat, $im_lon );
465
466     # print "Updating (common): $apt_id - $rwy\n";
467     $ILS{$apt_id . $rwy} = $record;
468     $AIRPORTS{$apt_id} = 1;
469 }
470
471
472 # update the $type of the record
473 sub update_type() {
474     my( $apt_id ) = shift;
475     my( $rwy ) = shift;
476     my( $new_type ) = shift;
477
478     my( $record );
479
480     if ( $ILS{$apt_id . $rwy} ne "" ) {
481         my( $type_code, $type_name, $apt_id, $rwy, $loc_freq, $loc_id,
482             $loc_hdg, $loc_lat, $loc_lon, $gs_elev, $gs_angle, $gs_lat,
483             $gs_lon, $dme_lat, $dme_lon, $om_lat, $om_lon, $mm_lat, $mm_lon,
484             $im_lat, $im_lon ) = split( /\s+/, $ILS{$apt_id . $rwy} );
485         # print "Updating type: $apt_id $rwy: $type_name -> $new_type\n";
486         $type_name = $new_type;
487         &update_record( $apt_id, $rwy, $type_name, $loc_freq, $loc_id,
488                         $loc_hdg, $loc_lat, $loc_lon, $gs_elev,
489                         $gs_angle, $gs_lat, $gs_lon, $dme_lat,
490                         $dme_lon, $om_lat, $om_lon, $mm_lat,
491                         $mm_lon, $im_lat, $im_lon );
492     } else {
493         die "Error, trying to update $apt_id - $rwy which doesn't exist\n";
494     }
495 }
496
497
498 # convert a lon/lat coordinate in various formats to signed decimal
499
500 sub make_dcoord() {
501     my($coord) = shift;
502     my( $dir, $deg, $min, $sec );
503     my( $value ) = 0.0;
504
505     $coord = &strip_ws( $coord );
506
507     if ( $coord =~ m/^[WE]/ ) {
508         ( $dir, $deg, $min, $sec )
509             = $coord =~ m/^([EW])(\d\d\d)(\d\d)(\d\d\d\d)/;
510         $value = $deg + $min/60.0 + ($sec/100)/3600.0;
511         if ( $dir eq "W" ) {
512             $value = -$value;
513         }
514     } elsif ( $coord =~ m/^[NS]/ ) {
515         ( $dir, $deg, $min, $sec )
516             = $coord =~ m/^([NS])(\d\d)(\d\d)(\d\d\d\d)/;
517         $value = $deg + $min/60.0 + ($sec/100)/3600.0;
518         if ( $dir eq "S" ) {
519             $value = -$value;
520         }
521     } elsif ( $coord =~ m/[EW]$/ ) {
522         ($value, $dir) = $coord =~ m/([\d\s\.]+)([EW])/;
523         if ( $dir eq "W" ) {
524             $value = -$value;
525         }
526     } elsif ( $coord =~ m/[NS]$/ ) {
527         ($value, $dir) = $coord =~ m/([\d\s\.]+)([NS])/;
528         if ( $dir eq "S" ) {
529             $value = -$value;
530         }
531     }
532     # print "$dir $deg:$min:$sec = $value\n";
533     return $value;
534 }
535
536 # convert a magnetic variation in various formats to signed decimal
537
538 sub make_dmagvar() {
539     my( $coord ) = shift;
540     my( $value );
541
542     if ( $coord =~ m/^[EW]/ ) {
543         my( $dir, $deg, $min, $date )
544             = $coord =~ m/^([EW])(\d\d\d)(\d\d\d) (\d\d\d\d)/;
545         $value = $deg + ($min/10)/60.0;
546         if ( $dir eq "W" ) {
547             $value = -$value;
548         }
549     } elsif ( $coord =~ m/[EW]$/ ) {
550         my( $deg, $dir )
551             = $coord =~ m/^(\d\d)([EW])/;
552         $value = $deg;
553         if ( $dir eq "W" ) {
554             $value = -$value;
555         }
556     }
557     # print "$dir $deg:$min = $value\n";
558
559     return $value;
560 }
561
562 # strip white space off front and back of string
563
564 sub strip_ws() {
565     my( $string ) = shift;
566     $string =~ s/^\s+//;
567     $string =~ s/\s+$//;
568     return $string;
569 }