#!/usr/bin/perl
# NAME: findapX.pl
# AIM: Read X-Plane (Robin Pell) apt.dat, and find an airport given the name,
# 21/08/2013 - Also output the airport frequency list
# 09/04/2013 - Add -2fg to output the found airport in FG apt.dat 8.10 format
# geoff mclane - http://geoffmclane.com/mperl/index.htm - 20100801
use strict;
use warnings;
use File::Copy;
use Math::Trig;
use File::Basename;  # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] )
use Time::HiRes qw( gettimeofday tv_interval );
my $os = $^O;
my $perl_dir = '/home/geoff/bin';
my $PATH_SEP = '/';
my $temp_dir = '/tmp';
if ($os =~ /win/i) {
    $perl_dir = 'C:\GTools\perl';
    $temp_dir = $perl_dir;
    $PATH_SEP = "\\";
}
unshift(@INC, $perl_dir);
require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl' Check paths in \@INC...\n";
require 'fg_wsg84.pl' or die "Unable to load fg_wsg84.pl ...\n";
require "Bucket2.pm" or die "Unable to load Bucket2.pm ...\n";
#require "Bucket.pm" or die "Unable to load Bucket.pm ...\n";
my $DSCNROOT = 'D:\Scenery\terrascenery\data\Scenery';
# =============================================================================
# This NEEDS to be adjusted to YOUR particular default location of these files.
my $FGROOT = "D:/FG/xplane/1000";
###my $FGROOT = "D:/SAVES/xplane";
my $SCENEROOT = (exists $ENV{'FG_SCENERY'})? $ENV{'FG_SCENERY'} : $DSCNROOT;
my $APT_FILE 	= "$FGROOT/apt.dat";	# the airports data file
###my $APT_FILE   = "$FGROOT/apt4.dat";	# the airports data file
my $NAV_FILE 	= "$FGROOT/earth_nav.dat";	# the NAV, NDB, etc. data file
my $FIX_FILE    = "$FGROOT/earth_fix.dat";	# the FIX data file
my $LIC_FILE    = "$FGROOT/AptNavGNULicence.txt";
# =============================================================================

# log file stuff
our ($LF);
my $pgmname = $0;
if ($pgmname =~ /(\\|\/)/) {
    my @tmpsp = split(/(\\|\/)/,$pgmname);
	$pgmname = $tmpsp[-1];
}
my $outfile = $temp_dir.$PATH_SEP."temp.$pgmname.txt";
open_log($outfile);

my $t0 = [gettimeofday];

#my $out_path = 'C:\FG\17\fgx-globe\apt1000';
my $out_path = $temp_dir.$PATH_SEP."temp-apts3";
#my $out_path = $temp_dir.$PATH_SEP."temp-apts2";
#my $out_path = $temp_dir.$PATH_SEP."temp-apts";

# program variables - set during running
# different searches -icao=LFPO, -latlon=1,2, or -name="airport name"
# KSFO San Francisco Intl (37.6208607739872,-122.381074803838)
my $aptdat = $APT_FILE;
my $navdat = $NAV_FILE;
my $licfil = $LIC_FILE;

my $SRCHICAO = 0;	# search using icao id ... takes precedence
my $SRCHONLL = 0;	# search using lat,lon
my $SRCHNAME = 0;	# search using name
my $SHOWNAVS = 0;	# show navaids around airport found

my $aptname = "strasbourg";
my $apticao = 'KSFO';
my $in_lat = 37.6;
my $in_lon = -122.4;
my $maxlatd = 0.5;
my $maxlond = 0.5;
my $nmaxlatd = 0.1;
my $nmaxlond = 0.1;
my $max_cnt = 0;	# maximum airport count - 0 = no limit
my $max_range_km = 5;   # range search using KILOMETERS
my $out_file = $temp_dir.$PATH_SEP."tempapt";
my $out_base = 'apt1000';

# features and options
my $tryharder = 0;  # Expand the search for NAVAID, until at least 1 found
my $usekmrange = 0; # search using KILOMETER range - see $max_range_km
my $sortbyfreq = 1; # sort NAVAIDS by FREQUENCY
my $load_log = 0;
my $show_fg_form = 0;
my $gen_threshold_xml = 0;

my $write_output = 1;
my $add_newline = 1;
my $skip_done_files = 0;

my $output_full_list = 0;
my $do_nav_filter = 1;
my $output_as_json = 0;

# variables for range using distance calculation
my $PI = 3.1415926535897932384626433832795029;
my $D2R = $PI / 180;
my $R2D = 180 / $PI;
my $ERAD = 6378138.12;
my $DIST_FACTOR = $ERAD;
#/** Meters to Nautical Miles.  1 nm = 6076.11549 feet */
my $METER_TO_NM = 0.0005399568034557235;
#/** Nautical Miles to Meters */
my $NM_TO_METER = 1852;
#/** Feet to Meters */
my $FEET_TO_METER = 0.3048;
#/** Meters to Feet */
my $METER_TO_FEET = 3.28083989501312335958;

my ($file_version);

my $av_apt_lat = 0;	# later will be $tlat / $ac;
my $av_apt_lon = 0; # later $tlon / $ac;

# apt.dat CODES - see http://x-plane.org/home/robinp/Apt850.htm for DETAILS
#my $aln =     '1';	# airport line
#my $rln =    '10';	# runways/taxiways line 810 OLD CODE
#my $sealn =  '16'; # Seaplane base header data.
#my $heliln = '17'; # Heliport header data.  

#my $rln =    '100';	# land runways
#my $water =  '101'; # Water runway
#my $heli =   '102'; # Helipad

# offsets into land runway array
#my $of_lat1 = 9;
#my $of_lon1 = 10;
#my $of_lat2 = 18;
#my $of_lon2 = 19;

my $twrln =  '14'; # Tower view location. 
my $rampln = '15'; # Ramp startup position(s) 
my $bcnln =  '18'; # Airport light beacons  
my $wsln =   '19'; # windsock
my $minatc = '50';
my $twrfrq = '54';	# like 12210 TWR
my $appfrq = '55';  # like 11970 ROTTERDAM APP
my $maxatc = '56';
my $lastln = '99'; # end of file

# nav.dat.gz CODES
my $navNDB = '2';
my $navVOR = '3';
my $navILS = '4';
my $navLOC = '5';
my $navGS  = '6';
my $navOM  = '7';
my $navMM  = '8';
my $navIM  = '9';
my $navVDME = '12';
my $navNDME = '13';
my @navset = ($navNDB, $navVOR, $navILS, $navLOC, $navGS, $navOM, $navMM, $navIM, $navVDME, $navNDME);
my @navtypes = qw( NDB VOR ILS LOC GS OM NM IM VDME NDME );

my $maxnnlen = 4;
my $actnav = '';
my $line = '';
my $apt = '';
my $alat = 0;
my $alon = 0;
my $glat = 0;
my $glon = 0;
my $rlat = 0;
my $rlon = 0;
my $dlat = 0;
my $dlon = 0;
my $diff = 0;
my $rwycnt = 0;
my $icao = '';
my $name = '';
my @aptlist = ();
my @aptlist2 = ();
my @navlist = ();
my @navlist2 = ();
my $totaptcnt = 0;
my $acnt = 0;
my @lines = ();
my $cnt = 0;
my $loadlog = 0;
my $outcount = 0;
my @tilelist = ();
my $max_range_used = $max_range_km; # if expanded usign tryharder this is final value
my @warnings = ();

# debug tests
# ===================
my $test_name = 0;	# to TEST a NAME search
my $def_name = "hong kong";

my $test_ll = 0;	# to TEST a LAT,LON search
my $def_lat = 37.6;
my $def_lon = -122.4;

my $test_icao = 0;	# to TEST an ICAO search
my $def_icao = 'LSTZ'; # 'VHHH'; ## 'KHAF';  ## LFPO'; ## 'KSFO';

# debug
my $dbg1 = 0;	# show airport during finding ...
my $dbg2 = 0;	# show navaid during finding ...
my $dbg3 = 0;	# show count after finding
my $verb3 = 0;
my $dbg10 = 0;  # show EVERY airport
my $dbg11 = 0;  # prt( "$name $icao runways $rwycnt\n" ) if ($dbg11);
# ===================

### program variables
my $verbosity = 0;

sub VERB1() { return $verbosity >= 1; }
sub VERB2() { return $verbosity >= 2; }
sub VERB5() { return $verbosity >= 5; }
sub VERB9() { return $verbosity >= 9; }

sub show_warnings($) {
    my ($val) = @_;
    if (@warnings) {
        prt( "\nGot ".scalar @warnings." WARNINGS...\n" );
        foreach my $itm (@warnings) {
           prt("$itm\n");
        }
        prt("\n");
    } else {
        prt( "\nNo warnings issued.\n\n" ) if (VERB9());
    }
}

sub pgm_exit($$) {
    my ($val,$msg) = @_;
    if (length($msg)) {
        $msg .= "\n" if (!($msg =~ /\n$/));
        prt($msg);
    }
    show_warnings($val);
    close_log($outfile,$load_log);
    exit($val);
}


sub prtw($) {
   my ($tx) = shift;
   $tx =~ s/\n$//;
   prt("$tx\n");
   push(@warnings,$tx);
}


########################################################################
### SUBS
sub trimall($) {	# version 20061127
	my ($ln) = shift;
	chomp $ln;			# remove CR (\n)
	$ln =~ s/\r$//;		# remove LF (\r)
	$ln =~ s/\t/ /g;	# TAB(s) to a SPACE
	$ln =~ s/\s\s/ /g while ($ln =~ /\s\s/); # all double space to SINGLE
	$ln = substr($ln,1) while ($ln =~ /^\s/); # remove all LEADING space
	$ln = substr($ln,0, length($ln) - 1) while ($ln =~ /\s$/); # remove all TRAILING space
	return $ln;
}

#Surface Type Code Surface type of runways or taxiways
#1 Asphalt
#2 Concrete
#3 Turf or grass
#4 Dirt (brown)
#5 Gravel (grey)
#12 Dry lakebed (eg. At KEDW) Example: KEDW (Edwards AFB)
#13 Water runways Nothing displayed
#14 Snow or ice Poor friction. Runway markings cannot be added.
#15 Transparent Hard surface, but no texture/markings (use in custom scenery)
# offset 2 in runway array
my %runway_surface = (
    1  => 'Asphalt',
    2  => 'Concrete',
    3  => 'Turf/grass',
    4  => 'Dirt',
    5  => 'Gravel',
    6  => 'H-Asphalt', # helepad (big 'H' in the middle).
    7  => 'H_Concrete', # helepad (big 'H' in the middle).
    8  => 'H_Turf', # helepad (big 'H' in the middle).
    9  => 'H_Dirt', # helepad (big 'H' in the middle). 
    10 => 'T_Asphalt', # taxiway - with yellow hold line across long axis (not available from WorldMaker).
    11 => 'T_Concrete', # taxiway - with yellow hold line across long axis (not available from WorldMaker).
    12 => 'Dry_Lakebed', # (eg. at KEDW Edwards AFB).
    13 => 'Water', # runways (marked with bobbing buoys) for seaplane/floatplane bases (available in X-Plane 7.0 and later). 
    14 => 'Snow',
    15 => 'Transparent'
);

my %frequency_code = (
    50 => "ATIS",   # 50 Recorded AWOS, ASOS or ATIS
    51 => "CTAF",   # 51 Unicom Unicom (US), CTAF (US), Radio (UK)
    52 => "CLD",    # 52 CLD Clearance Delivery
    53 => "GND",    # 53 GND Ground
    54 => "TWR",    # 54 TWR Tower
    55 => "APP",    # 55 APP Approach
    56 => "DEP"     # 56 DEP Departure
    );

sub get_current_threshold($$) {
    my ($icao,$rt) = @_;
    return 0 if (! -d $SCENEROOT);
    return 0 if (length($icao) < 3);
    my $dir = $SCENEROOT.$PATH_SEP."Airports";
    return 0 if (! -d $dir);
    $dir .= $PATH_SEP.substr($icao,0,1);
    $dir .= $PATH_SEP.substr($icao,1,1);
    $dir .= $PATH_SEP.substr($icao,2,1);
    $dir .= $PATH_SEP.$icao.".threshold.xml";
    return 0 if (! -f $dir);
    ${$rt} = $dir;
    return 1;
}

sub show_airports_found {
	my ($mx) = shift;	# limit the AIRPORT OUTPUT
	my $scnt = $acnt;
	my $tile = '';
    my ($off,$rra,$rwa,$rha,$rfa,$i);
    my ($j,$rcnt,$rrwy);
    my ($type,$wid,$surf,$code,$smth,$ctln,$elit);
    my ($rwy1,$lat1,$lon1,$rwy2,$lat2,$lon2,$rlen);
    my ($dist,$az1,$az2,$racnt,$hdg1,$hdg2);
    my ($displ1,$displ2,$stopw1,$stopw2,$xml,$file);
    my ($tmp);
	if ($mx && ($mx < $scnt)) {
		$scnt = $mx;
		prt( "Listing $scnt of $acnt aiports " );
	} else {
		prt( "Listing $scnt aiport(s) " );
	}

	if ($SRCHICAO) {
		prt( "with ICAO [$apticao] ...\n" );
	} elsif ($SRCHONLL) {
		prt( "around lat,lon [$in_lat,$in_lon], using diff [$maxlatd,$maxlond] ...\n" );
	} else {    # $SRCHNAME
		prt( "matching [$aptname] ...\n" );
	}
	for ($i = 0; $i < $scnt; $i++) {
        $xml = "<?xml version=\"1.0\"?>\n<PropertyList>\n";
        #                  0      1      2      3      4      5
        # push(@aptlist2, [$diff, $icao, $name, $alat, $alon, $off]);
		$diff = $aptlist2[$i][0];
		$icao = $aptlist2[$i][1];
		$name = $aptlist2[$i][2];
		$alat = $aptlist2[$i][3];
		$alon = $aptlist2[$i][4];
		$off  = $aptlist2[$i][5];
		$tile = get_bucket_info( $alon, $alat );

		$icao .= ' ' while (length($icao) < 4);
		$line = $diff;
		$line = ' '.$line while (length($line) < 6);
		#$line .= ' '.$icao.' '.$name.' ('.$alat.','.$alon.") tile=$tile";
		$line .= ' '.$icao.' '.$name.' ('.$alat.','.$alon.") tile=".get_tile($alon,$alat);
		prt("$line\n");
        #                0      1      2      3      4      5    6    7    8
        #push(@aptlist, [$diff, $icao, $name, $alat, $alon, \@r, \@w, \@h, \@f]);
        $rra = $aptlist[$off][5];   # runway ref array
        $rwa = $aptlist[$off][6];   # waterways ref array
        $rha = $aptlist[$off][7];   # helipad ref array
        $rfa = $aptlist[$off][8];   # freq ref array
        $rcnt = scalar @{$rra};
        if ($rcnt > 0) {
            if ($output_as_json) {
                $line = '{"runway_count":'.$rcnt.',"runways":['."\n";
            } else {
                $line = 'rwys='.$rcnt.":\n";
            }
            for ($j = 0; $j < $rcnt; $j++) {
                $rrwy = ${$rra}[$j];
                $racnt = scalar @{$rrwy};
                if ($racnt < 20) {
                    prtw("WARNING: icao=$icao: Array count $racnt! SKIPPING\n");
                    prt(join(",",@{$rrwy})."\n");
                    next;
                }
                $type = ${$rrwy}[0]; #0  - 100 for land runways
                $wid  = ${$rrwy}[1]; #1  - 29.87 - Width of runway in metres Two decimal places recommended. Must be >= 1.00
                $surf = ${$rrwy}[2]; #2  - 3 - Code defining the surface type (concrete, asphalt, etc) Integer value for a Surface Type Code
                $code = ${$rrwy}[3]; #3  - 0 - Code defining a runway shoulder surface type 0=no shoulder, 1=asphalt shoulder, 2=concrete shoulder
                $smth = ${$rrwy}[4]; #4  - 0.15 - Runway smoothness (not used by X-Plane yet) 0.00 (smooth) to 1.00 (very rough). Default is 0.25
                $ctln = ${$rrwy}[5]; #5  - 0 - Runway centre-line lights 0=no centerline lights, 1=centre line lights
                $elit = ${$rrwy}[6]; #6  - 0 - Runway edge lighting (also implies threshold lights) 0=no edge lights, 2=medium intensity edge lights
                # #7  - 1 - Auto-generate distance-remaining signs (turn off if created manually) 0=no auto signs, 1=auto-generate signs
                # #The following fields are repeated for each end of the runway
                $rwy1 = ${$rrwy}[8]; #8  - 13L - Runway number (eg. 31R, 02). Leading zeros are required. Two to three characters. Valid suffixes: L, R or C (or blank)
                $lat1 = sprintf("%.8f",${$rrwy}[9]); #9  - 47.53801700 - Latitude of runway threshold (on runway centerline) in decimal degrees Eight decimal places supported
                $lon1 = sprintf("%.8f",${$rrwy}[10]); #10 - -122.30746100 - Longitude of runway threshold (on runway centerline) in decimal degrees Eight decimal places supported
                $displ1 = ${$rrwy}[11]; # #11 - 73.15 - Length of displaced threshold in metres (this is included in implied runway length) Two decimal places (metres). Default is 0.00
                $stopw1 = ${$rrwy}[12]; # #12 - 0.00 - Length of overrun/blast-pad in metres (not included in implied runway length) Two decimal places (metres). Default is 0.00
                # #13 - 2 - Code for runway markings (Visual, non-precision, precision) Integer value for Runway Marking Code
                # #14 - 0 - Code for approach lighting for this runway end Integer value for Approach Lighting Code
                # #15 - 0 - Flag for runway touchdown zone (TDZ) lighting 0=no TDZ lighting, 1=TDZ lighting
                # #16 - 1 - Code for Runway End Identifier Lights (REIL) 0=no REIL, 1=omni-directional REIL, 2=unidirectional REIL
                $rwy2 = ${$rrwy}[17]; #17 - 31R
                $lat2 = sprintf("%.8f",${$rrwy}[18]); #18 - 47.52919200
                $lon2 = sprintf("%.8f",${$rrwy}[19]); #19 - -122.30000000
                $displ2 = ${$rrwy}[20]; # #20 - 110.95 
                $stopw2 = ${$rrwy}[21]; # #21 - 0.00 
                # #22 - 2
                # #23 - 0
                # #24 - 0
                # #25 - 1
                $rlen = fg_geo_inverse_wgs_84($lat1, $lon1, $lat2, $lon2, \$az1, \$az2, \$dist);
                $hdg1 = $az1;
                $hdg2 = $az2;
                if ($output_as_json) {
                    $line .= '{';
                    $line .= '"len_m":'.int($dist);
                    $line .= ',"wid_m":'.$wid;
                    $line .= ',"hdg":'.int($hdg1);
                    $line .= ',"surf":"';
                    if (defined $runway_surface{$surf}) {
                        $line .= $runway_surface{$surf};
                    } else {
                        $line .= $surf;
                    }
                    $line .= '"';
                    $line .= ',"rwy1":"'.$rwy1.'"';
                    $line .= ',"lat1":'.$lat1;
                    $line .= ',"lon1":'.$lon1;
                    $line .= ',"displ1":'.$displ1;
                    $line .= ',"stopw1":'.$stopw1;
                    $line .= ',"rwy2":"'.$rwy2.'"';
                    $line .= ',"lat2":'.$lat2;
                    $line .= ',"lon2":'.$lon2;
                    $line .= "}";
                    if (($j + 1) < $rcnt) {
                        $line .= ',';
                    } else {
                        $line .= ']';
                    }
                    $line .= "\n";
                } else {
                    $line .= int($dist * $METER_TO_FEET)."x".int($wid*$METER_TO_FEET)." ft, hdg=".int($az1).",";
                    if (defined $runway_surface{$surf}) {
                        $line .= $runway_surface{$surf};
                    } else {
                        $line .= $surf;
                    }
                    my $dsp1 = int($displ1*$METER_TO_FEET).','.int($stopw1*$METER_TO_FEET);
                    my $dsp2 = int($displ2*$METER_TO_FEET).','.int($stopw2*$METER_TO_FEET);
                    $line .= " $rwy1 $lat1,$lon1 ($dsp1) $rwy2 $lat2,$lon2 ($dsp2)\n";
                }
                if ($gen_threshold_xml) {
                    my $lato1 = $lat1;
                    my $lono1 = $lon2;
                    my $lato2 = $lat2;
                    my $lono2 = $lon2;
                    my ($s);
                    if (($displ1 + $stopw1) > 0) {
                        $s = ($displ1 + $stopw1) * $FEET_TO_METER;
                        fg_geo_direct_wgs_84( $lat1, $lon1, $hdg1, $s, \$lato1, \$lono1, \$az2 );
                    }
                    $xml .= "  <runway>\n";
                    $xml .= "    <threshold>\n";
                    $xml .= "      <rwy>$rwy1</rwy>\n";
                    $xml .= "      <lat>$lato1</lat>\n";
                    $xml .= "      <lon>$lono1</lon>\n";
                    $xml .= "      <hdg-deg>$hdg1</hdg-deg>\n";
                    $xml .= "      <displ-m>";
                    $xml .= $displ1 * $FEET_TO_METER;
                    $xml .= "</displ-m>\n";
                    $xml .= "      <stopw-m>";
                    $xml .= $stopw1 * $FEET_TO_METER;
                    $xml .= "</stopw-m>\n";
                    $xml .= "    </threshold>\n";
                    if (($displ2 + $stopw2) > 0) {
                        $s = ($displ2 + $stopw2) * $FEET_TO_METER;
                        fg_geo_direct_wgs_84( $lat2, $lon2, $hdg2, $s, \$lato2, \$lono2, \$az2 );
                    }
                    $xml .= "    <threshold>\n";
                    $xml .= "      <rwy>$rwy2</rwy>\n";
                    $xml .= "      <lat>$lato2</lat>\n";
                    $xml .= "      <lon>$lono2</lon>\n";
                    $xml .= "      <hdg-deg>$hdg2</hdg-deg>\n";
                    $xml .= "      <displ-m>";
                    $xml .= $displ2 * $FEET_TO_METER;
                    $xml .= "</displ-m>\n";
                    $xml .= "      <stopw-m>";
                    $xml .= $stopw2 * $FEET_TO_METER;
                    $xml .= "</stopw-m>\n";
                    $xml .= "    </threshold>\n";
                    $xml .= "  </runway>\n";
                }
            }
            prt($line);
        }

		$outcount++;
		add_2_tiles($tile);
        if ($gen_threshold_xml) {
    		$icao = $aptlist2[$i][1]; # get ICAO back
            $xml .= "</PropertyList>\n";
            $file = $temp_dir.$PATH_SEP."temp.".$icao.".threshold.xml";
            rename_2_old_bak($file);    # rename any previous
            write2file($xml,$file);
            prt("Threshold XML written to [$file]\n");
            if (get_current_threshold($icao,\$tmp)) {
                prt("Compare this with [$tmp]\n");
            }
        }
        # 20130821 - show frequencies
        # $rfa = $aptlist[$off][8];   # freq ref array
        # array like
        # 50 12082 LFMV ATIS
        # 54 12260 AVIGNON TWR
        # 55 12087 AVIGNON APP
        # my %frequency_code = (
        # 50 => "ATIS",   # 50 Recorded AWOS, ASOS or ATIS
        # 51 => "CTAF",   # 51 Unicom Unicom (US), CTAF (US), Radio (UK)
        # 52 => "CLD",    # 52 CLD Clearance Delivery
        # 53 => "GND",    # 53 GND Ground
        # 54 => "TWR",    # 54 TWR Tower
        # 55 => "APP",    # 55 APP Approach
        # 56 => "DEP"     # 56 DEP Departure
        # );
        my ($freq);
        $rcnt = scalar @{$rfa};
        prt("Freq:$rcnt: ");
        for ($j = 0; $j < $rcnt; $j++) {
            $rrwy = ${$rfa}[$j];
            $type = ${$rrwy}[0];
            $freq = ${$rrwy}[1] / 100;
            if (defined $frequency_code{$type}) {
                $type = $frequency_code{$type};
            }
            prt("$type $freq ");
        }
        prt("\n");

	}   # for EACH airport
	### prt( "Done $scnt list ...\n" );
}


sub get_tile { # $alon, $alat
	my ($lon, $lat) = @_;
	my $tile = 'e';
	if ($lon < 0) {
		$tile = 'w';
		$lon = -$lon;
	}
	my $ilon = int($lon / 10) * 10;
	if ($ilon < 10) {
		$tile .= "00$ilon";
	} elsif ($ilon < 100) {
		$tile .= "0$ilon";
	} else {
		$tile .= "$ilon"
	}
	if ($lat < 0) {
		$tile .= 's';
		$lat = -$lat;
	} else {
		$tile .= 'n';
	}
	my $ilat = int($lat / 10) * 10;
	if ($ilat < 10) {
		$tile .= "0$ilat";
	} elsif ($ilon < 100) {
		$tile .= "$ilat";
	} else {
		$tile .= "$ilat"
	}
	return $tile;
}

sub add_2_tiles {	# $tile
	my ($tl) = shift;
	if (@tilelist) {
		foreach my $t (@tilelist) {
			if ($t eq $tl) {
				return 0;
			}
		}
	}
	push(@tilelist, $tl);
	return 1;
}

sub is_valid_nav {
	my ($tx) = shift;
    if ($tx && length($tx)) {
        my $txt = "$tx";
        my $cnt = 0;
        foreach my $n (@navset) {
            $cnt++;
            if ($n eq $txt) {
                $actnav = $navtypes[$cnt];
                return $cnt;
            }
        }
    }
	return 0;
}

sub set_average_apt_latlon {
	my $ac = scalar @aptlist2;
	my $tlat = 0;
	my $tlon = 0;
	if ($ac) {
		for (my $i = 0; $i < $ac; $i++ ) {
			$alat = $aptlist2[$i][3];
			$alon = $aptlist2[$i][4];
			$tlat += $alat;
			$tlon += $alon;
		}
		$av_apt_lat = $tlat / $ac;
		$av_apt_lon = $tlon / $ac;
	}
}

# push(@aptlist2, [$diff, $icao, $name, $alat, $alon]);
# my $nmaxlatd = 1.5;
# my $nmaxlond = 1.5;
sub near_an_airport {
	my ($lt, $ln, $dist, $az) = @_;
    my ($az1, $az2, $s, $ret);
	my $ac = scalar @aptlist2;
    my ($x,$y,$z) = fg_ll2xyz($ln,$lt);    # get cart x,y,z
    my $d2 = $max_range_km * 1000;      # get meters
	for (my $i = 0; $i < $ac; $i++ ) {
		$diff = $aptlist2[$i][0];
		$icao = $aptlist2[$i][1];
		$name = $aptlist2[$i][2];
		$alat = $aptlist2[$i][3];
		$alon = $aptlist2[$i][4];
        if ($usekmrange) {
            my ($xb, $yb, $yz) = fg_ll2xyz($alon, $alat);
            my $dst = sqrt( fg_coord_dist_sq( $x, $y, $z, $xb, $yb, $yz ) ) * $DIST_FACTOR;
            if ($dst < $d2) {
                $s = -1;
                $az1 = -1;
                $ret = fg_geo_inverse_wgs_84($alat, $alon, $lt, $ln, \$az1, \$az2, \$s);
                $$dist = $s;
                $$az = $az1;
                return ($i + 1);
            }
        } else {
    		my $td = abs($lt - $alat);
	    	my $nd = abs($ln - $alon);
		    if (($td < $nmaxlatd)&&($nd < $nmaxlond)) {
                $s = -1;
                $az1 = -1;
                $ret = fg_geo_inverse_wgs_84($alat, $alon, $lt, $ln, \$az1, \$az2, \$s);
                $$dist = $s;
                $$az = $az1;
			    return ($i + 1);
		    }
        }
	}
	return 0;
}

sub show_navaids_found {
	my ($ic, $in, $line, $lcnt, $dnone);
	my ($diff, $icao, $alat, $alon);
	my ($typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name, $off);
    my ($dist, $az, $adkm, $ahdg);
    my $hdr = "Type  Latitude     Logitude        Alt.  Freq.  Range  Frequency2    ID  Name";
	#prt( "$actnav, $typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name ($off)\n");
	#push(@navlist2, [$typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name, $off]);
	my $ac = scalar @aptlist2;
    @navlist2 = sort mycmp_ascend_n4 @navlist2 if ($sortbyfreq);
	my $nc = scalar @navlist2;
    my %freqs = ();
    my @offs = ();
    for ( $in = 0; $in < $nc; $in++ ) {
        $nfrq = $navlist2[$in][4];
        if (!defined $freqs{$nfrq}) {
            push(@offs,$in);
            $freqs{$nfrq} = 1
        }
    }
    $nc = scalar @offs;
	prt( "For $ac ap, found $nc NAVAID freqs, " );
    if ($usekmrange) {
        $ahdg = sprintf("%.1f",$max_range_used);
    	prt( "within [$ahdg] Km ...\n" );
    } else {
    	prt( "within [$nmaxlatd,$nmaxlond] degrees ...\n" );
    }
	$lcnt = 0;
	for ($ic = 0; $ic < $ac; $ic++) {
		$diff = $aptlist2[$ic][0];
		$icao = $aptlist2[$ic][1];
		$name = $aptlist2[$ic][2];
		$alat = $aptlist2[$ic][3];
		$alon = $aptlist2[$ic][4];
		$icao .= ' ' while (length($icao) < 4);
		$line = $diff;
		$line = ' '.$line while (length($line) < 6);
		$line .= ' '.$icao.' '.$name.' ('.$alat.','.$alon.')';
        prt("\n") if ($ic);
		prt("$line\n");
		$outcount++;
		$dnone = 0;
		### for ( $in = 0; $in < $nc; $in++ ) {
        foreach $in (@offs) {
			$typ = $navlist2[$in][0];
			$nlat = $navlist2[$in][1];
			$nlon = $navlist2[$in][2];
			$nalt = $navlist2[$in][3];
			$nfrq = $navlist2[$in][4];
			$nrng = $navlist2[$in][5];
			$nfrq2 = $navlist2[$in][6];
			$nid = $navlist2[$in][7];
			$name = $navlist2[$in][8];
			$off = $navlist2[$in][9];
            $dist = $navlist2[$in][10];
            $az = $navlist2[$in][11];
			if ($off == ($ic + 1)) {
				# it is FOR this airport
				is_valid_nav($typ);
    			#     NDB  50.049000, 008.328667,   490,   399,    25,      0.000,  WBD, Wiesbaden NDB (ap=2 nnnKm on 270.1)
                #     Type Latitude   Logitude     Alt.  Freq.  Range  Frequency2    ID  Name
                #     VOR  37.61948300, -122.37389200,    13, 11580,    40,       17.0,  SFO, SAN FRANCISCO VOR-DME (ap=1 nnnKm on 1.1)
				#prt( "$actnav, $typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name ($off)\n");
				$line = $actnav;
				$line .= ' ' while (length($line) < $maxnnlen);
				$line .= ' ';
				$nalt = ' '.$nalt while (length($nalt) < 5);
				$nfrq = ' '.$nfrq while (length($nfrq) < 5);
				$nrng = ' '.$nrng while (length($nrng) < 5);
				$nfrq2 = ' '.$nfrq2 while (length($nfrq2) < 10);
				$nid = ' '.$nid while (length($nid) < 4);
                $nlat = ' '.$nlat while (length($nlat) < 12);
                $nlon = ' '.$nlon while (length($nlon) < 13);
                $adkm = sprintf( "%0.2f", ($dist / 1000.0));    # get kilometers
                $ahdg = sprintf( "%0.1f", $az );    # and azimuth
				$line .= "$nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name (ap=$off ".$adkm."Km on $ahdg)";
				if ($dnone == 0) {
					#prt( "Type  Latitude     Logitude        Alt.  Freq.  Range  Frequency2    ID  Name\n" );
					prt( "$hdr\n" );
					$dnone = 1;
				}
				prt( "$line\n" );
				$outcount++;
				$lcnt++;
				add_2_tiles( get_bucket_info( $nlon, $nlat ) );
			}
		}
		prt( "$hdr\n" ) if ($dnone && ($outcount > 10));
	}
	prt( "Listed $lcnt NAVAIDS ...\n" );
}

sub set_apt_version($$) {
    my ($ra,$cnt) = @_;
    if ($cnt < 5) {
        prt("ERROR: Insufficient lines to be an apt.dat file!\n");
        exit(1);
    }
    my $line = trimall(${$ra}[0]);
    if ($line ne 'I') {
        prt("ERROR: File does NOT begin with an 'I'!\n");
        exit(1);
    }
    $line = trimall(${$ra}[1]);
    if ($line =~ /^(\d+)\s+Version\s+/i) {
        $file_version = $1;
        prt("Dealing with file version [$file_version]\n");
    } else {
        prt("ERROR: File does NOT begin with Version info!\n");
        exit(1);

    }
}

# sort by type
sub mycmp_ascend_n0 {
   return -1 if (${$a}[0] < ${$b}[0]);
   return  1 if (${$a}[0] > ${$b}[0]);
   return 0;
}


# sort by ICAO text
sub mycmp_ascend_n1 {
   if (${$a}[1] lt ${$b}[1]) {
      return -1;
   }
   if (${$a}[1] gt ${$b}[1]) {
      return 1;
   }
   return 0;
}

# put least first
sub mycmp_ascend_n4 {
   if (${$a}[4] < ${$b}[4]) {
      return -1;
   }
   if (${$a}[4] > ${$b}[4]) {
      return 1;
   }
   return 0;
}

sub mycmp_ascend_n5 {
   if (${$a}[5] < ${$b}[5]) {
      return -1;
   }
   if (${$a}[5] > ${$b}[5]) {
      return 1;
   }
   return 0;
}

sub mycmp_ascend_n6 {
   if (${$a}[6] < ${$b}[6]) {
      return -1;
   }
   if (${$a}[6] > ${$b}[6]) {
      return 1;
   }
   return 0;
}

# put least first
sub mycmp_ascend {
   if (${$a}[0] < ${$b}[0]) {
      prt( "-[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
      return -1;
   }
   if (${$a}[0] > ${$b}[0]) {
      prt( "+[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
      return 1;
   }
   prt( "=[".${$a}[0]."] == [".${$b}[0]."]\n" ) if $verb3;
   return 0;
}

sub mycmp_decend {
   if (${$a}[0] < ${$b}[0]) {
      prt( "+[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
      return 1;
   }
   if (${$a}[0] > ${$b}[0]) {
      prt( "-[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
      return -1;
   }
   prt( "=[".${$a}[0]."] == [".${$b}[0]."]\n" ) if $verb3;
   return 0;
}


sub fix_airport_name($) {
    my $name = shift;
    my @arr = split(/\s/,$name);
    my $nname = '';
    my $len;
    foreach $name (@arr) {
        $nname .= ' ' if (length($nname));
        $nname .= uc(substr($name,0,1));
        $len = length($name);
        if ($len > 1) {
            $nname .= lc(substr($name,1));
        }
    }
    return $nname;
}

sub in_world_range($$) {
    my ($lat,$lon) = @_;
    if (($lat <= 90)&&
        ($lat >= -90)&&
        ($lon <= 180)&&
        ($lon >= -180)) {
        return 1;
    }
    return 0;
}


sub find_ils_for_apt($) {
    my $find = shift;
    #                0     1     2     3     4     5    6     7   8     9    10
    # push(@navlist,[$type,$nlat,$nlon,$feet,$freq,$rng,$bear,$id,$icao,$rwy,$name]);
    my $max = scalar @navlist;
    my ($i,$ra,$icao,$cnt,$type);
    $cnt = 0;
    for ($i = 0; $i < $max; $i++) {
        $cnt++;
        $ra = $navlist[$i];
        $type = ${$ra}[0];
        $icao = ${$ra}[8];
        next if ($type != 6);
        return $cnt if ($find eq $icao);
    }
    return 0;
}


# 1000 Version - data cycle 2012.08, build 20121293
sub load_apt_data {
    prt("Loading $aptdat file ...\n");
    mydie("ERROR: Can NOT locate $aptdat ...$!...\n") if ( !( -f $aptdat) );
    ###open IF, "<$aptdat" or mydie("OOPS, failed to open [$aptdat] ... check name and location ...\n");
    open IF, "<$aptdat" or mydie( "ERROR: CAN NOT OPEN $aptdat...$!...\n" );
    @lines = <IF>;
    close IF;
    $cnt = scalar @lines;
    prt( "Processing $cnt lines ... airports, runways, and taxiways ...\n" ) if (VERB9());
    set_apt_version( \@lines, $cnt );
    if ($SRCHICAO) {
        prt( "Searching for ICAO [$apticao] ...\n" );
    } elsif ($SRCHONLL) {
        prt( "Searching for LAT,LON [$in_lat,$in_lon], using max. diff. [$maxlatd,$maxlond] ...\n" );
    } else {    # $SRCHNAME (default)
        prt( "Searching for NAME [$aptname] ...\n" );
    }
    my ($rlat1,$rlat2,$rlon1,$rlon2,$type,$len,$lnn,$add,$off);
    my @runways = ();
    my $wwcnt = 0;
    my @waterways = ();
    my $helicnt = 0;
    my @heliways = ();
    my $trwycnt = 0;
    my @freqs = ();
    foreach $line (@lines) {
        $lnn++;
        $line = trimall($line);
        $len = length($line);
        next if ($lnn < 3); # skip first two lines
        next if ($len == 0);
        ###prt("$line\n");
        my @arr = split(/\s+/,$line);
        $type = $arr[0];
        if ($line =~ /^99\s?/) {	# 99, followed by space, count 0 or more ...
            prt( "Reached END OF FILE (99)... \n" ) if ($dbg1);
            last;
        } elsif (($line =~ /^1\s+/)||($line =~ /^16\s+/)||($line =~ /^17\s+/)) {	# start with '1'
            $trwycnt = $rwycnt;
            $trwycnt += $wwcnt;
            $trwycnt += $helicnt;
            if (length($apt) && ($trwycnt > 0)) {
                $alat = $glat / $trwycnt;
                $alon = $glon / $trwycnt;
                $dlat = abs( $in_lat - $alat );
                $dlon = abs( $in_lon - $alon );
                $diff = int( ($dlat * 10) + ($dlon * 10) );
                #       0 1  2 3 4    5+
                # like: 1 28 1 0 VHHH Hong Kong Intl
                my @arr2 = split(/\s+/,$apt);
                $icao = $arr2[4];
                $name = join(' ', splice(@arr2,5));
                prt( "[$name] $icao $alat $alon rwys=$rwycnt\n" ) if ($dbg11);
                ##prt("$diff [$apt] (with $rwycnt runways at [$alat, $alon]) ...\n");
                ##prt("$diff [$icao] [$name] ...\n");
                $off = scalar @aptlist;
                my @ra = @runways;
                my @wa = @waterways;
                my @ha = @heliways;
                my @fa = @freqs;
                #               0      1      2      3      4      5     6     7     8
                push(@aptlist, [$diff, $icao, $name, $alat, $alon, \@ra, \@wa, \@ha, \@fa ]);
                $add = 0;
                if ($SRCHICAO) {
                    $add = 1 if ($icao =~ /$apticao/);
                } else {
                    if ($SRCHONLL) {
                        $add = 1 if (($dlat < $maxlatd) && ($dlon < $maxlond));
                    } else {    # $SRCHNAME (default)
                        $add = 1 if ($name =~ /$aptname/i);
                    }
                }
                if ($add) {
                    prt("$icao, $name, $alat, $alon \n") if ($dbg1);
                    push(@aptlist2, [$diff, $icao, $name, $alat, $alon, $off]);
                }
            }

            $apt = $line;   # set the AIRPORT line
            $rwycnt = 0;    # restart RUNWAY counter
            @runways = ();
            $glat = 0;
            $glon = 0;
            $wwcnt = 0;
            @waterways = ();
            $helicnt = 0;
            @heliways = ();
            @freqs = ();
            $totaptcnt++;	# count another AIRPORT
            prt("$apt\n") if ($dbg10);
        } elsif ($line =~ /^100\s+/) {
            $rlat1 = $arr[9];  # $of_lat1
            $rlon1 = $arr[10]; # $of_lon1
            $rlat2 = $arr[18]; # $of_lat2
            $rlon2 = $arr[19]; # $of_lon2
            $rlat = ($rlat1 + $rlat2) / 2;
            $rlon = ($rlon1 + $rlon2) / 2;
            ###prt( "$line [$rlat, $rlon]\n" );
            $glat += $rlat;
            $glon += $rlon;
            my @a2 = @arr;
            push(@runways, \@a2);
            $rwycnt++;
        } elsif ($type == 101) {	# Water runways
            # 0   1      2 3  4           5             6  7           8
            # 101 243.84 0 16 29.27763293 -089.35826258 34 29.26458929 -089.35340410
            # 101 22.86  0 07 29.12988952 -089.39561501 25 29.13389936 -089.38060001
            # prt("$.: $line\n");
            $rlat1 = $arr[4];
            $rlon1 = $arr[5];
            $rlat2 = $arr[7];
            $rlon2 = $arr[8];
            $rlat = sprintf("%.8f",(($rlat1 + $rlat2) / 2));
            $rlon = sprintf("%.8f",(($rlon1 + $rlon2) / 2));
            if (!in_world_range($rlat,$rlon)) {
                prtw( "WARNING: $.: $line [$rlat, $rlon] NOT IN WORLD\n" );
                next;
            }
            $glat += $rlat;
            $glon += $rlon;
            my @a2 = @arr;
            push(@waterways, \@a2);
            $wwcnt++;
        } elsif ($type == 102) {	# Heliport
            # my $heli =   '102'; # Helipad
            # 0   1  2           3            4      5     6     7 8 9 10   11
            # 102 H2 52.48160046 013.39580674 355.00 18.90 18.90 2 0 0 0.00 0
            # 102 H3 52.48071507 013.39937648 2.64   13.11 13.11 1 0 0 0.00 0
            # prt("$.: $line\n");
            $rlat = sprintf("%.8f",$arr[2]);
            $rlon = sprintf("%.8f",$arr[3]);
            if (!in_world_range($rlat,$rlon)) {
                prtw( "WARNING: $.: $line [$rlat, $rlon] NOT IN WORLD\n" );
                next;
            }
            $glat += $rlat;
            $glon += $rlon;
            my @a2 = @arr;
            push(@heliways, \@a2);
            $helicnt++;
        } elsif (($type >= 50)&&($type <= 56)) { # 50–56 Communication frequencies Zero, one or many for each airport
            # 50 12082 LFMV ATIS
            my @tfa = @arr;
            push(@freqs, \@tfa); # save the freq array
        }
    }

    # do any LAST entry
    $trwycnt = $rwycnt;
    $trwycnt += $wwcnt;
    $trwycnt += $helicnt;
    if ($trwycnt > 0) {
        $alat = $glat / $trwycnt;
        $alon = $glon / $trwycnt;
        $dlat = abs( $in_lat - $alat );
        $dlon = abs( $in_lon - $alon );
        $diff = int( ($dlat * 10) + ($dlon * 10) );
        #       0 1  2 3 4    5+
        # like: 1 28 1 0 VHHH Hong Kong Intl
        my @arr2 = split(/\s/,$apt);
        $icao = $arr2[4];
        $name = join(' ', splice(@arr2,5));
        prt( "[$name] $icao $alat $alon rwys=$rwycnt\n" ) if ($dbg11);
        ##prt("$diff [$apt] (with $rwycnt runways at [$alat, $alon]) ...\n");
        ##prt("$diff [$icao] [$name] ...\n");
        $off = scalar @aptlist;
        my @r = @runways;
        my @w = @waterways;
        my @h = @heliways;
        my @f = @freqs;
        push(@aptlist, [$diff, $icao, $name, $alat, $alon, \@r, \@w, \@h, \@f]);
        $add = 0;
        if ($SRCHICAO) {
            $add = 1 if ($icao =~ /$apticao/);
        } else {
            if ($SRCHONLL) {
                $add = 1 if (($dlat < $maxlatd) && ($dlon < $maxlond));
            } else {    # $SRCHNAME (default)
                $add = 1 if ($name =~ /$aptname/i);
            }
        }
        if ($add) {
            prt("$icao, $name, $alat, $alon \n") if ($dbg1);
            push(@aptlist2, [$diff, $icao, $name, $alat, $alon, $off]);
        }
        $totaptcnt++;	# count another AIRPORT
    }
    $diff = scalar @aptlist2;
    prt("Loaded $totaptcnt airports... found $diff matching...\n");

}

sub load_nav_file {
	prt("\nLoading $navdat file ...\n");
	mydie("ERROR: Can NOT locate [$navdat]!\n") if ( !( -f $navdat) );
	open NIF, "<$navdat" or mydie( "ERROR: CAN NOT OPEN $navdat...$!...\n" );
	my @nav_lines = <NIF>;
	close NIF;
    return @nav_lines;
}

sub search_nav {
	my ($typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name, $off);
    my ($alat, $alon);
    my ($dist, $az,$tmp);
    my @nav_lines = load_nav_file();
	my $nav_cnt = scalar @nav_lines;
	my $ac = scalar @aptlist2;
	prt("Processing $nav_cnt lines ...\n");
    $max_range_used = $max_range_km;
    $tmp = sprintf("%.1f",$max_range_used);
    if ($ac == 1) {
   		$alat = $aptlist2[0][3];
		$alon = $aptlist2[0][4];
        if ($usekmrange) {

            prt("Using distance of [$tmp] Km from $ac airport at $alat,$alon.\n" );
        } else {
            prt("Using deviation of [$nmaxlatd,$nmaxlond] from $ac airport at $alat,$alon.\n" );
        }
    } else {
        if ($usekmrange) {
            prt("Using maximum distance of [$tmp] from $ac airports.\n" );
        } else {
            prt("Using maximum lat,lon deviation of [$nmaxlatd,$nmaxlond] from $ac airports.\n" );
        }
    }
	my $vcnt = 0;
    my $navs_found = 0;
    my (@arr, $nc);
	foreach $line (@nav_lines) {
		$line = trimall($line);
		###prt("$line\n");
		@arr = split(/ /,$line);
		# 0   1 (lat)   2 (lon)        3     4   5           6   7  8++
		# 2   38.087769 -077.324919  284   396  25       0.000 APH  A P Hill NDB
		# 3   57.103719  009.995578   57 11670 100       1.000 AAL  Aalborg VORTAC
		# 4   39.980911 -075.877814  660 10850  18     281.662 IMQS 40N 29 ILS-cat-I
		# 4  -09.458922  147.231225  128 11010  18     148.650 IWG  AYPY 14L ILS-cat-I
		# 5   40.034606 -079.023281 2272 10870  18     236.086 ISOZ 2G9 24 LOC
		# 5   67.018506 -050.682072  165 10955  18      61.600 ISF  BGSF 10 LOC
		# 6   39.977294 -075.860275  655 10850  10  300281.205 ---  40N 29 GS
		# 6  -09.432703  147.216444  128 11010  10  302148.785 ---  AYPY 14L GS
		# 7   39.960719 -075.750778  660     0   0     281.205 ---  40N 29 OM
		# 7  -09.376150  147.176867  146     0   0     148.785 JSN  AYPY 14L OM
		# 8  -09.421875  147.208331   91     0   0     148.785 MM   AYPY 14L MM
		# 8  -09.461050  147.232544  146     0   0     328.777 PY   AYPY 32R MM
		# 9   65.609444 -018.052222   32     0   0      22.093 ---  BIAR 01 IM
		# 9   08.425319  004.475597 1126     0   0      49.252 IL   DNIL 05 IM
		# 12 -09.432703  147.216444   11 11010  18       0.000 IWG  AYPY 14L DME-ILS
		# 12 -09.449222  147.226589   11 10950  18       0.000 IBB  AYPY 32R DME-ILS
		$nc = scalar @arr;
		$typ = $arr[0];
        # Check for type number in @navset, and set $actnav to name, like VOR, NDB, etc
		if ( is_valid_nav($typ) ) {
			$vcnt++;
			$nlat = $arr[1];
			$nlon = $arr[2];
			$nalt = $arr[3];
			$nfrq = $arr[4];
			$nrng = $arr[5];
			$nfrq2 = $arr[6];
			$nid = $arr[7];
			$name = '';
			for (my $i = 8; $i < $nc; $i++) {
				$name .= ' ' if length($name);
				$name .= $arr[$i];
			}
			push(@navlist, [$typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name]);
            # Using $nmaxlatd, $nmaxlond, check airports in @aptlist2;
			$off = near_an_airport( $nlat, $nlon, \$dist, \$az );
			if ($off) {
				prt( "$actnav, $typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name ($off)\n") if ($dbg2);
				push(@navlist2, [$typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name, $off, $dist, $az]);
			}
        ### } elsif ($line =~ /^\d+\s+Version\s+-\s+DAFIF\s+/) {
        } elsif ($line =~ /^\d+\s+Version\s+-\s+/) {
            my $ind = index($line,',');
            prt( "NAVAID: ".substr($line, 0, (($ind > 0) ? $ind : 50) )."\n" );   # 810 Version - data cycle 2012.08, ...
        } elsif (($line eq 'I')||($line eq '99')) {
            # ignore these
		} elsif (length($line)) {
            prtw("WARNING: What is this line? [$line]???\n");
        }
	}
    $navs_found = scalar @navlist2;
    if (($navs_found == 0) && $tryharder) {
        my $def_latd = $nmaxlatd;
        my $def_lond = $nmaxlond;
        my $def_dist = $max_range_km;
        while ($navs_found == 0) {
            $nmaxlatd += 0.1;
            $nmaxlond += 0.1;
            $max_range_km += 0.1;
            if ($usekmrange) {
                $tmp = sprintf("%.1f",$max_range_km);
                prt("Expanded to [$tmp] Km from $ac airport(s)...\n" ) if (VERB9());
            } else {
                prt("Expanded to [$nmaxlatd,$nmaxlond] from $ac airport(s)...\n" ) if (VERB9());
            }
            foreach $line (@nav_lines) {
                $line = trimall($line);
                ###prt("$line\n");
                @arr = split(/ /,$line);
                $nc = scalar @arr;
                $typ = $arr[0];
                # Check for type number in @navset, and set $actnav to name, like VOR, NDB, etc
                if ( is_valid_nav($typ) ) {
                    $nlat = $arr[1];
                    $nlon = $arr[2];
                    $nalt = $arr[3];
                    $nfrq = $arr[4];
                    $nrng = $arr[5];
                    $nfrq2 = $arr[6];
                    $nid = $arr[7];
                    $name = '';
                    for (my $i = 8; $i < $nc; $i++) {
                        $name .= ' ' if length($name);
                        $name .= $arr[$i];
                    }
                    # Using $nmaxlatd, $nmaxlond, check airports in @aptlist2;
                    $off = near_an_airport( $nlat, $nlon, \$dist, \$az );
                    if ($off) {
                        prt( "$actnav, $typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name ($off)\n") if ($dbg2);
                        push(@navlist2, [$typ, $nlat, $nlon, $nalt, $nfrq, $nrng, $nfrq2, $nid, $name, $off, $dist, $az]);
                    }
                }
            }
            $navs_found = scalar @navlist2;
        }
        prt("Found $navs_found nearby NAVAIDS, ");
        if ($usekmrange) {
            $tmp = sprintf("%.1f",$max_range_km);
            prt("using distance $tmp Km...\n" );
        } else {
            prt("using difference $nmaxlatd, $nmaxlond...\n" );
        }
        $max_range_used = $max_range_km;
        $nmaxlatd = $def_latd;
        $nmaxlond = $def_lond;
        $max_range_km = $def_dist;
    }
	prt("Done - Found $navs_found nearby NAVAIDS, of $vcnt searched...\n" );
}


##############
### functions

# 12/12/2008 - Additional distance calculations
# from 'signs' perl script
# Melchior FRANZ <mfranz # aon : at>
# $Id: signs,v 1.37 2005/06/01 15:53:00 m Exp $

# sub ll2xyz($$) {
sub ll2xyz {
	my $lon = (shift) * $D2R;
	my $lat = (shift) * $D2R;
	my $cosphi = cos $lat;
	my $di = $cosphi * cos $lon;
	my $dj = $cosphi * sin $lon;
	my $dk = sin $lat;
	return ($di, $dj, $dk);
}


# sub xyz2ll($$$) {
sub xyz2ll {
	my ($di, $dj, $dk) = @_;
	my $aux = $di * $di + $dj * $dj;
	my $lat = atan2($dk, sqrt $aux) * $R2D;
	my $lon = atan2($dj, $di) * $R2D;
	return ($lon, $lat);
}

# sub coord_dist_sq($$$$$$) {
sub coord_dist_sq {
	my ($xa, $ya, $za, $xb, $yb, $zb) = @_;
	my $x = $xb - $xa;
	my $y = $yb - $ya;
	my $z = $zb - $za;
	return $x * $x + $y * $y + $z * $z;
}


# Ensure argument exists, or die.
sub require_arg {
    my ($arg, @arglist) = @_;
    mydie( "ERROR: no argument given for option '$arg' ...\n" ) if ! @arglist;
}


sub get_bucket_info {
   my ($lon,$lat) = @_;
   my $b = Bucket2->new();
   $b->set_bucket($lon,$lat);
   return $b->bucket_info();
}

sub look_like_icao($) {
    my $icao = shift;
    my $up = uc($icao);
    my $len = length($icao);
    if (($len == 4) && ($up eq $icao)) {
        return 1;
    }
    return 0;
}

# How can I tell if a string is a number?
# The simplest method is:
#         if ($string == "$string") { 
#          # It is a number
#        } 
# Note the use of the == operator to compare the string to its numeric value. 
# However, this approach is dangerous because the $string might contain arbitrary 
# code such as @{[system "rm -rf /"]} which would be executed as a result of the 
# interpolation process. For safety, use this regular expression:
#   if ($var =~ /(?=.)M{0,3}(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})/) {
#    print "$var contains a number.\b";
#  }


# contains digits,commas and 1 period AND
# does not contain alpha's, more than 1 period
# commas or periods at the beggining and ends of
# each line AND
# is not null
sub IsANumber($) {
    my $var = shift;
    if ( ( $var =~ /(^[0-9]{1,}?$)|(\,*?)|(\.{1})/ ) &&
         !( $var =~ /([a-zA-Z])|(^[\.\,]|[\.\,]$)/ ) &&
         ($var ne '') ) {
         return 1;
    } 
    return 0;
}


sub looks_like_rwy($) {
    my $rwy = shift;
    if (length($rwy) > 0) {
        my $ch = substr($rwy,0,1);
        if (IsANumber($ch)) {  # or perhaps if ($ch == "$ch")
            return 1;
        }
    }
    return 0;
        }

sub parse_nav_lines($) {
    my $rnava = shift;
    my $max = scalar @{$rnava};
    # add to my @navlist = ();
    my ($i,$line,$lnn,@arr,$acnt,$type,$len,$vnav);
    my ($nlat,$nlon,$feet,$freq,$rng,$bear,$id,$icao,$name,$rwy);
    $lnn = 0;
    prt("Processing $max line of NAV data...\n") if (VERB9());
    for ($i = 0; $i < $max; $i++) {
        $line = ${$rnava}[$i];
        chomp $line;
        $line = trimall($line);
        $len = length($line);
        $lnn++;
        next if ($len == 0);
        if ($lnn < 3) {
            if ($lnn == 2) {
                prt(substr($line,0,50)."\n");
            }
            next;
        }
        @arr = split(/\s+/,$line);
        $acnt = scalar @arr;
        $type = $arr[0];
        if ($type == 99) {
            prt("$lnn: Reached EOF (99)\n");
            last;
    }
        #0  1           2             3    4     5   6          7    8                 9    10
        #CD LAT         LON           ELEV FREQ  RNG BEARING    ID   NAME              RWY  NAME
        #                             FT         NM. GS Ang          ICAO
        #2  47.63252778 -122.38952778 0      362 50  0.0        BF   NOLLA NDB
        #3  47.43538889 -122.30961111 354  11680 130 19.0       SEA  SEATTLE VORTAC
        #4  47.42939200 -122.30805600 338  11030 18  180.343    ISNQ KSEA               16L ILS-cat-I
        #6  47.46081700 -122.30939400 425  11030 10  300180.343 ISNQ KSEA               16L GS
        if ($acnt < 9) {
            prt("Split only yielded $acnt!\n");
            prt("$lnn: [$line]\n");
            pgm_exit(1,"");
}

        $nlat = $arr[1];
        $nlon = $arr[2];
        $feet = $arr[3];
        $freq = $arr[4];
        $rng  = $arr[5];
        $bear = $arr[6];
        $id   = $arr[7];
        $icao = $arr[8];
        $name = $icao;
        $rwy  = '';
        if ($type == 2) {
            # 2  NDB - (Non-Directional Beacon) Includes NDB component of Locator Outer Markers (LOM)
            # 2  47.63252778 -122.38952778 0      362 50  0.0        BF   NOLLA NDB
            $icao = '';
            $name = join(' ', splice(@arr,8));
        } elsif ($type == 3) {
            # 3  VOR - (including VOR-DME and VORTACs) Includes VORs, VOR-DMEs and VORTACs
            # 3  47.43538889 -122.30961111 354  11680 130 19.0       SEA  SEATTLE VORTAC
            $icao = '';
            $name = join(' ', splice(@arr,8));
        } elsif ($type == 4) {
            # 4  ILS - LOC Localiser component of an ILS (Instrument Landing System)
            # 0  1           2             3    4     5   6          7    8                  9   10
            # 4  47.42939200 -122.30805600 338  11030 18  180.343    ISNQ KSEA               16L ILS-cat-I
            $rwy = $arr[9];
            $name = $arr[10];
        } elsif ($type == 5) {
            # 5  LOC - Localiser component of a localiser-only approach Includes for LDAs and SDFs
            # 5  40.03460600 -079.02328100  2272 10870 18  236.086   ISOZ 2G9                25  LOC
            # 5  67.01850600 -050.68207200   165 10955 18   61.600   ISF  BGSF               10  LOC
            $rwy = $arr[9];
            $name = $arr[10];
        } elsif ($type == 6) {
            # 6  GS  - Glideslope component of an ILS Frequency shown is paired frequency, not the DME channel
            # 6  47.46081700 -122.30939400 425  11030 10  300180.343 ISNQ KSEA               16L GS
            $rwy = $arr[9];
            $name = $arr[10];
        } elsif ($type == 7) {
            # 7  OM  - Outer markers (OM) for an ILS Includes outer maker component of LOMs
            $rwy = $arr[9];
            $name = $arr[10];
        } elsif ($type == 8) {
            # 8  MM  - Middle markers (MM) for an ILS
            # 8  47.47223300 -122.31102500 433  0     0   180.343    ---- KSEA               16L MM
            $rwy = $arr[9];
            $name = $arr[10];
        } elsif ($type == 9) {
            # 9  IM  - Inner markers (IM) for an ILS
            $rwy = $arr[9];
            $name = $arr[10];
        } elsif ($type == 12) {
            # 12 DME - including the DME component of an ILS, VORTAC or VOR-DME Frequency display suppressed on X-Plane’s charts
            # 0  1           2             3    4     5   6          7    8                  9   10
            # 12 47.43433300 -122.30630000 369  11030 18  0.000      ISNQ KSEA               16L DME-ILS
            # 12 47.43538889 -122.30961111 354  11680 130 0.0        SEA  SEATTLE VORTAC DME
            if (($acnt > 10) && look_like_icao($icao) && looks_like_rwy($arr[9])) {
                $rwy = $arr[9];
                $name = $arr[10];
            } else {
                ###prt("$lnn: Split $acnt! [$line]\n");
                $icao = ''; # this is NOT an ICAO
                $name = join(' ', splice(@arr,8));
            }
        } elsif ($type == 13) {
            # 13 Stand-alone DME, or the DME component of an NDB-DME Frequency will displayed on X-Plane’s charts
            # 0  1           2             3    4     5      6       7    8
            # 13 57.10393300  009.99280800  57  11670 199    0.0     AAL  AALBORG TACAN
            # 13 68.71941900 -052.79275300 172  10875  25    0.0     AS   AASIAAT DME
            $icao = '';
            $name = join(' ', splice(@arr,8));
        } else {
            prt("$lnn: INVALID [$line]\n");
            next;
        }
        #               0     1     2     3     4     5    6     7   8     9   10
        push(@navlist,[$type,$nlat,$nlon,$feet,$freq,$rng,$bear,$id,$icao,$rwy,$name]);
    }
    my (@sorted,$ra,$diff,$ofile);
    @sorted = sort mycmp_ascend_n0 @navlist;
    $ofile = $out_file."-nav.csv";
    $diff = scalar @sorted;
    prt("Loaded $diff navaids... writing to $ofile\n");
    $line = "type,lat,lon,feet,freq,rng,bear,id,icao,rwy,name\n";
    #                0     1     2     3     4     5    6     7   8     9    10
    # push(@navlist,[$type,$nlat,$nlon,$feet,$freq,$rng,$bear,$id,$icao,$rwy,$name]);
    for ($i = 0; $i < $diff; $i++) {
        $ra = $sorted[$i];
        $type = ${$ra}[0];
        $nlat = ${$ra}[1];
        $nlon = ${$ra}[2];
        $feet = ${$ra}[3];
        $freq = ${$ra}[4];
        $rng  = ${$ra}[5];
        $bear = ${$ra}[6];
        $id   = ${$ra}[7];
        $icao = ${$ra}[8];
        $rwy  = ${$ra}[9];
        $name = ${$ra}[10];
        $line .= "$type,$nlat,$nlon,$feet,$freq,$rng,$bear,$id,$icao,$rwy,$name\n";
    }
    write2file($line,$ofile);
    prt("nav CSV written to [$ofile]\n");
}

############################################
### MAIN ###
############################################
###prt( "$pgmname ... Hello, World ... ".scalar localtime(time())."\n" );
parse_args(@ARGV);	# collect command line arguments ...
load_apt_data();
$acnt = scalar @aptlist2;
prt( "Found $acnt, of $totaptcnt, airports ...\n" ) if ($dbg3);
set_average_apt_latlon();
if ($SRCHICAO) {
	prt( "Found $acnt matching $apticao ...(av. $av_apt_lat,$av_apt_lon)\n" ) if ($dbg3);
} elsif ($SRCHONLL) {
	prt( "Found $acnt matching $in_lat, $in_lon ...(av. $av_apt_lat,$av_apt_lon)\n" ) if ($dbg3);
} else {    # $SRCHNAME
	prt( "Found $acnt matching $aptname ... (av. $av_apt_lat,$av_apt_lon)\n" ) if ($dbg3);
}
#my @aptsort = sort mycmp_ascend @aptlist;
show_airports_found($max_cnt);
if ($acnt && $SHOWNAVS) {
	search_nav();
	show_navaids_found();
    show_airports_found($max_cnt);
}
$cnt = scalar @tilelist;
if ($cnt) {
	prt( "Scenery Tile" );
	if ($cnt > 1) {
		prt( "s" );
	}
	prt( ": " );
	foreach $name (@tilelist) {
		prt( "$name " );
	}
	prt( "\n" );
}
show_warnings(0);
my $elapsed = tv_interval ( $t0, [gettimeofday]);
prt( "Ran for $elapsed seconds ...\n" );
$loadlog = 1 if (($outcount > 30) || $dbg10 || $dbg11);
close_log($outfile,$loadlog);
###unlink($outfile);
exit(0);
############################################
############################################

sub give_help {
	prt( "FLIGHTGEAR AIRPORT SEARCH UTILITY\n" );
	prt( "Usage: $pgmname options\n" );
	prt( "Options: A ? anywhere for this help.\n" );
	prt( " -icao=$apticao          - Search using icao.\n" );
	prt( " -latlon=$in_lat,$in_lon - Search using latitude, longitude.\n" );
	prt( " -maxout=$max_cnt           - Limit the airport output. A 0 for ALL.\n" );
	prt( " -maxll=$maxlatd,$maxlond      - Maximum difference, when searching ariports using lat,lon.\n" );
	prt( " -name=\"$aptname\"  - Search using airport name. (A -name=. would match all.)\n" );
	prt( " -shownavs (or -s)   - Show NAVAIDS around airport found, if any. " );
    prt( "(Def=". ($SHOWNAVS ? "On" : "Off") . ")\n" );
	prt( " -nmaxll=$nmaxlatd,$nmaxlond     - Maximum difference, when searching NAVAID lat,lon.\n" );
    prt( " -range=$max_range_km            - Set Km range when checking for NAVAIDS.\n" );
    prt( " -r                  - Use above range ($max_range_km Km) for searching.\n" );
    prt( " -tryhard (or -t)    - Expand search if no NAVAIDS found in range. " );
    prt( "(Def=". ($tryharder ? "On" : "Off") . ")\n" );
    prt( " -2fg                - Output in FG 8.10 apt.dat format, if found.\n" );
    prt( " -v[1-9]             - Bump, or set verbosity. (def=$verbosity)\n");
    prt( " -xml                - Output threshold xml file. (def=$gen_threshold_xml)\n");
    prt( "Data Files Used:\n");
	prt( " -aptdata=$aptdat - Use a specific AIRPORT data file.\n" );
	prt( " -navdata=$navdat - Use a specific NAVAID data file.\n" );
	mydie( "                                     Happy Searching.\n" );
}

# set $SRCHICAO on/off
# set $SRCHONLL on/off
sub parse_args {
	my (@av) = @_;
	my (@arr,$sarg);
	while(@av) {
		my $arg = $av[0]; # shift @av;
        my $lcarg = lc($arg);
        $sarg = $arg;
        if ($sarg =~ /^-/) {
            $sarg = substr($sarg,1) while ($sarg =~ /^-/);
            if ($arg =~ /\?/) {
                give_help();
            # BY ICAO
            } elsif ( $arg =~ /^-icao=(.+)$/i ) {
                $apticao = $1;
                $SRCHICAO = 1;
                $SRCHONLL = 0;
                $SRCHNAME = 0;
                prt("[v1] Search using ICAO of [$apticao] ...\n" ) if (VERB1());
            } elsif ( $lcarg eq '-icao' ) {
                require_arg(@av);
                shift @av;
                $SRCHICAO = 1;
                $SRCHONLL = 0;
                $SRCHNAME = 0;
                $apticao = $av[0];
                prt("[v1] Search using ICAO of [$apticao] ...\n" ) if (VERB1());

            # BY LAT,LON
            } elsif ( $arg =~ /^-latlon=(.+)$/i ) {
                $SRCHICAO = 0;
                $SRCHONLL = 1;
                $SRCHNAME = 0;
                @arr = split(',', $1);
                if (scalar @arr == 2) {
                    $in_lat = $arr[0];
                    $in_lon = $arr[1];
                    prt("[v1] Search using LAT,LON of [$in_lat,$in_lon] ...\n" ) if (VERB1());
                } else {
                    mydie( "ERROR: Failed to find lat,lon in [$arg]...\n" );
                }
            } elsif ( $lcarg eq '-latlon' ) {
                require_arg(@av);
                shift @av;
                $SRCHICAO = 0;
                $SRCHONLL = 1;
                $SRCHNAME = 0;
                @arr = split(',', $av[0]);
                if (scalar @arr == 2) {
                    $in_lat = $arr[0];
                    $in_lon = $arr[1];
                    prt("[v1] Search using LAT,LON of [$in_lat,$in_lon] ...\n" ) if (VERB1());
                } else {
                    mydie( "ERROR: Failed to find lat,lon in [$arg]...\n" );
                }

            # By NAME
            } elsif ( $arg =~ /^-name=(.+)$/i ) {
                $aptname = $1;
                $SRCHICAO = 0;
                $SRCHONLL = 0;
                $SRCHNAME = 1;
                prt("[v1] Search using NAME of [$aptname] ...\n" ) if (VERB1());
            } elsif ( $lcarg eq '-name' ) {
                require_arg(@av);
                shift @av;
                $SRCHICAO = 0;
                $SRCHONLL = 0;
                $SRCHNAME = 1;
                $aptname = $av[0];
                prt("[v1] Search using NAME of [$aptname] ...\n" ) if (VERB1());
            } elsif ( $arg =~ /^-loadlog$/i ) {
                $loadlog = 1;
                prt( "[v1] Load log into wordpad.\n" ) if (VERB1());
            } elsif ( $arg =~ /^-shownavs$/i ) {
                $SHOWNAVS = 1;
                prt( "[v1] And show NAVAIDS around airport, if any.\n" ) if (VERB1());
            } elsif ( $arg =~ /^-s$/i ) {
                $SHOWNAVS = 1;
                prt( "[v1] And show NAVAIDS around airport, if any.\n" ) if (VERB1());
            } elsif ( $arg =~ /^-maxll=(.+)$/i ) {
                @arr = split(',', $1);
                if (scalar @arr == 2) {
                    $maxlatd = $arr[0];
                    $maxlond = $arr[1];
                    prt("[v1] Search maximum difference LAT,LON of [$maxlatd,$maxlond] ...\n") if (VERB1());
                } else {
                    mydie( "ERROR: Failed to find maximum lat,lon difference in [$arg]...\n" );
                }
            } elsif ( $lcarg eq '-maxll' ) {
                require_arg(@av);
                shift @av;
                @arr = split(',', $av[0]);
                if (scalar @arr == 2) {
                    $maxlatd = $arr[0];
                    $maxlond = $arr[1];
                    prt("[v1] Search maximum difference LAT,LON of [$maxlatd,$maxlond] ...\n" ) if (VERB1());
                } else {
                    mydie( "ERROR: Failed to find maximum lat,lon difference in [$arg]...\n" );
                }
            } elsif ( $arg =~ /^-nmaxll=(.+)$/i ) {
                @arr = split(',', $1);
                if (scalar @arr == 2) {
                    $nmaxlatd = $arr[0];
                    $nmaxlond = $arr[1];
                    prt("[v1] Search maximum difference LAT,LON of [$nmaxlatd,$nmaxlond] ...\n" ) if (VERB1());
                } else {
                    mydie( "ERROR: Failed to find maximum lat,lon difference in [$arg]...\n" );
                }
            } elsif ( $lcarg eq '-nmaxll' ) {
                require_arg(@av);
                shift @av;
                @arr = split(',', $av[0]);
                if (scalar @arr == 2) {
                    $nmaxlatd = $arr[0];
                    $nmaxlond = $arr[1];
                    prt("[v1] Search maximum difference LAT,LON of [$nmaxlatd,$nmaxlond] ...\n" ) if (VERB1());
                } else {
                    mydie( "ERROR: Failed to find maximum lat,lon difference in [$arg]...\n" );
                }
            } elsif ( $arg =~ /^-aptdata=(.+)$/i ) {
                $aptdat = $1;	# the airports data file
                prt("[v1] Using AIRPORT data file [$aptdat] ...\n" ) if (VERB1());
            } elsif ( $lcarg eq '-aptdata' ) {
                require_arg(@av);
                shift @av;
                $aptdat = $av[0];	# the airports data file
                prt("[v1] Using AIRPORT data file [$aptdat] ...\n" ) if (VERB1());
            } elsif ( $arg =~ /^-navdata=(.+)$/i ) {
                $navdat = $1;
                prt("[v1] Using NAVAID data file [$navdat] ...\n" ) if (VERB1());
            } elsif ( $lcarg eq '-navdata' ) {
                require_arg(@av);
                shift @av;
                $navdat = $av[0];
                prt("[v1] Using NAVAID data file [$navdat] ...\n" ) if (VERB1());
            } elsif ( $arg =~ /^-maxout=(.+)$/i ) {
                $max_cnt = $1;
                prt("[v1] Airport output limited to $max_cnt. A zero (0), for no limit\n" ) if (VERB1());
            } elsif ( $lcarg eq '-maxout' ) {
                require_arg(@av);
                shift @av;
                $max_cnt = $av[0];
                prt("[v1] Airport output limited to $max_cnt. A zero (0), for no limit\n" ) if (VERB1());
            } elsif ( $arg =~ /^-range=(.+)$/i ) {
                $max_range_km = $1;
                $max_range_used = $max_range_km;
                prt("[v1] Navaid search using $max_range_km Km. A zero (0), for no limit\n" ) if (VERB1());
                $usekmrange = 1;
                prt("[v1] Navaid search using $max_range_km Km.\n" ) if (VERB1());
            } elsif ( $lcarg eq '-range' ) {
                require_arg(@av);
                shift @av;
                $max_range_km = $av[0];
                prt("[v1] Navaid search using $max_range_km Km. A zero (0), for no limit\n" ) if (VERB1());
                $usekmrange = 1;
                prt("[v1] Navaid search using $max_range_km Km.\n" ) if (VERB1());
                $max_range_used = $max_range_km;
            } elsif ( $lcarg eq '-r' ) {
                $usekmrange = 1;
                prt("[v1] Navaid search using $max_range_km Km.\n" ) if (VERB1());
            } elsif (( $lcarg eq '-tryhard' )||( $lcarg eq '-t' )) {
                $tryharder = 1;  # Expand the search for NAVAID, until at least 1 found
                prt("[v1] Navaid search 'tryharder' set.\n" ) if (VERB1());
            } elsif ($sarg =~ /^2/) {
                $show_fg_form = 1;
                prt("[v1] Set to show FG 8.10 form if found.\n") if (VERB1());
            } elsif ($sarg =~ /^x/) {
                $gen_threshold_xml = 1;
                prt("[v1] Set to generate threshold xml, if found.\n") if (VERB1());
            } elsif ($sarg =~ /^v/) {
                if ($sarg =~ /^v.*(\d+)$/) {
                    $verbosity = $1;
                } else {
                    while ($sarg =~ /^v/) {
                        $verbosity++;
                        $sarg = substr($sarg,1);
                    }
                }
                prt("[v1] Set verbosity to $verbosity\n") if (VERB1());
            } else {
                mydie( "ERROR: Unknown argument [$arg]. Try ? for HELP ...\n" );
            }
        } else {
            # bare argument
            # ASSUME AN AIRPORT NAME, unless exactly 4 upper case
            if ((length($arg) == 4)&&(uc($arg) eq $arg)) {
                $SRCHICAO = 1;
                $SRCHONLL = 0;
                $SRCHNAME = 0;
                $apticao = $arg;
                prt("[v1] Search using ICAO of [$apticao]\n" ) if (VERB1());
            } else {
                $SRCHICAO = 0;
                $SRCHONLL = 0;
                $SRCHNAME = 1;
                $aptname = $av[0];
                prt("[v1] Search using NAME of [$aptname]\n" ) if (VERB1());
            }

        }
		shift @av;
	}

	# *** ONLY FOR TESTING ***
	if ($test_name) {
		$SRCHICAO = 0;
		$SRCHONLL = 0;
		$SRCHNAME = 1;
		$SHOWNAVS = 1;
        $usekmrange = 1;
        $max_range_km = 5;
		$aptname = $def_name;
        prtw("WARNING: DEBUG test_name is ON, finding [$aptname]\n");
	} elsif ($test_ll) {
		$in_lat = $def_lat;
		$in_lon = $def_lon;
		$maxlatd = 0.1;
		$maxlond = 0.1;
		$SRCHICAO = 0;
		$SRCHONLL = 1;
		$SRCHNAME = 0;
        prtw("WARNING: DEBUG test_ll is ON, finding [$in_lat,$in_lon], max $maxlatd,$maxlond\n");
	} elsif ($test_icao) {
		$SRCHICAO = 1;
		$SRCHONLL = 0;
		$SRCHNAME = 0;
		$SHOWNAVS = 1;
		$apticao = $def_icao;
        # now have $tryharder to expand this, if NO NAVAIDS found
		$tryharder = 1;
        $usekmrange = 1;
        $show_fg_form = 1;
        prtw("WARNING: DEBUG test_icao is ON, finding [$apticao], with show navs, tryharder, usekmrange, show_fg_form.\n");
    }
    $max_range_used = $max_range_km;

	if ( ($SRCHICAO == 0) && ($SRCHONLL == 0) && ($SRCHNAME == 0) ) {
		prt( "ERROR: No valid command action found, like -\n" );
        prt( "By ICAO -icao=KSFO, by LAT/LON -latlon=21,-122, or NAME -name=something!\n" );
		give_help();
	}
}

# eof - findap02.pl

# X-Plane apt.dat codes
my $x_code = <<EOF

FROM : http://data.x-plane.com/designers.html#Formats

Airports:  1000 Version (latest - revised Mar 2012), 850 Version (expired) 715 Version (expired)
================================================================================================
from : http://data.x-plane.com/file_specs/XP%20APT1000%20Spec.pdf
1    Land airport header
16   Seaplane base header
17   Heliport header
100  Runway
101  Water runway
102  Helipad
110  Pavement (taxiway or ramp) header Must form a closed loop
120  Linear feature (painted line or light string) header Can form closed loop or simple string
130  Airport boundary header Must form a closed loop
111  Node All nodes can also include a “style” (line or lights)
112  Node with Bezier control point Bezier control points define smooth curves
113  Node with implicit close of loop Implied join to first node in chain
114  Node with Bezier control point, with implicit close of loop Implied join to first node in chain
115  Node terminating a string (no close loop) No “styles” used
116  Node with Bezier control point, terminating a string (no close loop) No “styles” used
14   Airport viewpoint One or none for each airport
15   Aeroplane startup location *** Convert these to new row code 1300 ***
18   Airport light beacon One or none for each airport
19   Windsock Zero, one or many for each airport
20   Taxiway sign (inc. runway distance-remaining signs) Zero, one or many for each airport
21   Lighting object (VASI, PAPI, Wig-Wag, etc.) Zero, one or many for each airport
1000 Airport traffic flow Zero, one or many for an airport. Used if following rules met (rules of same type are ORed together, rules of a different type are ANDed together to). First flow to pass all rules is used.
1001 Traffic flow wind rule One or many for a flow. Multiple rules of same type “ORed”
1002 Traffic flow minimum ceiling rule Zero or one rule for each flow
1003 Traffic flow minimum visibility rule Zero or one rule for each flow
1004 Traffic flow time rule One or many for a flow. Multiple rules of same type “ORed”
1100 Runway-in-use arrival/departure constraints First constraint met is used. Sequence matters
1101 VFR traffic pattern Zero or one pattern for each traffic flow
1200 Header indicating that taxi route network data follows
1201 Taxi route network node Sequencing is arbitrary. Must be part of one or more edges
1202 Taxi route network edge Must connect two nodes
1204 Taxi route edge active zone Can refer to up to 4 runway ends
1300 Airport location Not explicitly connected to taxi route network
50 – 56 Communication frequencies Zero, one or many for each airport


Airport line
#   0 1  2 3 4    5++
EG: 1 70 0 0 YSVS 1770
0  - Row code for an airport, seaplane base or heliport 1, 16 or 17
1  - Elevation of airport in feet above mean sea level (AMSL)
2  - Flag for control tower (used only in the X-Plane ATC system) 0=no ATC tower, 1=has ATC tower
3  - Deprecated. Use default value (“0”) Use 0
4  - Airport ICAO code. If no ICAO code exists, use FAA code (USA only) Maximum four characters. Must be unique.
5+ - Airport name. May contain spaces. Do not use special (accented) characters Text string (up to 40 characters)
Runway line
#   0   1     2 3 4    5 6 7 8   9            10            11    12   13 14 15 16 17  18           19           20     21   22 23 24 25
EG: 100 29.87 3 0 0.00 0 0 0 16  -24.20505300 151.89156100  0.00  0.00 1  0  0  0  34  -24.19732300 151.88585300 0.00   0.00 1  0  0  0
OR: 100 29.87 1 0 0.15 0 2 1 13L 47.53801700  -122.30746100 73.15 0.00 2  0  0  1  31R 47.52919200 -122.30000000 110.95 0.00 2  0  0  1
Land Runway
0  - 100 - Row code for a land runway (the most common) 100
1  - 29.87 - Width of runway in metres Two decimal places recommended. Must be >= 1.00
2  - 3 - Code defining the surface type (concrete, asphalt, etc) Integer value for a Surface Type Code
3  - 0 - Code defining a runway shoulder surface type 0=no shoulder, 1=asphalt shoulder, 2=concrete shoulder
4  - 0.15 - Runway smoothness (not used by X-Plane yet) 0.00 (smooth) to 1.00 (very rough). Default is 0.25
5  - 0 - Runway centre-line lights 0=no centerline lights, 1=centre line lights
6  - 0 - Runway edge lighting (also implies threshold lights) 0=no edge lights, 2=medium intensity edge lights
7  - 1 - Auto-generate distance-remaining signs (turn off if created manually) 0=no auto signs, 1=auto-generate signs

The following fields are repeated for each end of the runway

8  - 13L - Runway number (eg. 31R, 02). Leading zeros are required. Two to three characters. Valid suffixes: L, R or C (or blank)
9  - 47.53801700 - Latitude of runway threshold (on runway centerline) in decimal degrees Eight decimal places supported
10 - -122.30746100 - Longitude of runway threshold (on runway centerline) in decimal degrees Eight decimal places supported
11 - 73.15 - Length of displaced threshold in metres (this is included in implied runway length) Two decimal places (metres). Default is 0.00
12 - 0.00 - Length of overrun/blast-pad in metres (not included in implied runway length) Two decimal places (metres). Default is 0.00
13 - 2 - Code for runway markings (Visual, non-precision, precision) Integer value for Runway Marking Code
14 - 0 - Code for approach lighting for this runway end Integer value for Approach Lighting Code
15 - 0 - Flag for runway touchdown zone (TDZ) lighting 0=no TDZ lighting, 1=TDZ lighting
16 - 1 - Code for Runway End Identifier Lights (REIL) 0=no REIL, 1=omni-directional REIL, 2=unidirectional REIL

17 - 31R
18 - 47.52919200
19 - -122.30000000
20 - 110.95 
21 - 0.00 
22 - 2
23 - 0
24 - 0
25 - 1

Nav-aids: 810 version (latest - revised Oct 2011), 740 Version (expired)
========================================================================
from : http://data.x-plane.com/file_specs/XP%20NAV810%20Spec.pdf
for earth_nav.dat

2  NDB - (Non-Directional Beacon) Includes NDB component of Locator Outer Markers (LOM)
3  VOR - (including VOR-DME and VORTACs) Includes VORs, VOR-DMEs and VORTACs
4  ILS - LOC Localiser component of an ILS (Instrument Landing System)
5  LOC - Localiser component of a localiser-only approach Includes for LDAs and SDFs
6  GS  - Glideslope component of an ILS Frequency shown is paired frequency, not the DME channel
7  OM  - Outer markers (OM) for an ILS Includes outer maker component of LOMs
8  MM  - Middle markers (MM) for an ILS
9  IM  - Inner markers (IM) for an ILS
12 DME - including the DME component of an ILS, VORTAC or VOR-DME Frequency display suppressed on X-Plane’s charts
13 Stand-alone DME, or the DME component of an NDB-DME Frequency will displayed on X-Plane’s charts

Sample data
0  1           2             3    4     5   6          7    8                 9    10
CD LAT         LON           ELEV FREQ  RNG BEARING    ID   NAME              RWY  NAME
                             FT         NM. GS Ang          ICAO
2  47.63252778 -122.38952778 0      362 50  0.0        BF   NOLLA NDB
3  47.43538889 -122.30961111 354  11680 130 19.0       SEA  SEATTLE VORTAC
4  47.42939200 -122.30805600 338  11030 18  180.343    ISNQ KSEA               16L ILS-cat-I
6  47.46081700 -122.30939400 425  11030 10  300180.343 ISNQ KSEA               16L GS
8  47.47223300 -122.31102500 433  0     0   180.343    ---- KSEA               16L MM
12 47.43433300 -122.30630000 369  11030 18  0.000      ISNQ KSEA               16L DME-ILS
12 47.43538889 -122.30961111 354  11680 130 0.0        SEA  SEATTLE VORTAC DME
13 57.10393300  009.99280800  57  11670 199    0.0     AAL  AALBORG TACAN
13 68.71941900 -052.79275300 172  10875  25    0.0     AS   AASIAAT DME

Fixes: 600 Version (latest - revised July 2009)
===============================================
from : http://data.x-plane.com/file_specs/XP%20FIX600%20Spec.pdf
for earth_fix.dat
Sample
37.428522 -097.419194 ACESI

Airways:  640 Version (latest)
==============================
from : http://data.x-plane.com/file_specs/Awy640.htm
sample
ABCDE  32.283733 -106.898669 ABC    33.282503 -107.280542 2 180 450 J13
ABC    33.282503 -107.280542 DEF    35.043797 -106.816314 2 180 450 J13
DEF    35.043797 -106.816314 KLMNO  35.438056 -106.649536 2 180 450 J13-J14-J15 

ABCDE       Name of intersection or nav-aid at the beginning of this segment (the fix ABCDE in this example). 
32.283733   Latitude of the beginning of this segment. 
-106.898669 Longitude of the beginning of this segment. 
ABC         Name of intersection or nav-aid at the end of this segment (the nav-aid ABC in this example). 
33.282503   Latitude of the end of this segment. 
-107.280542 Longitude of the end of this segment. 
2           This is a "High" airway (1 = "low", 2 = "high").  If an airway segment is both High and Low, then it should be listed twice (once in each category).  This determines if the airway is shown on X-Plane's "High Enroute" or "Low Enroute" charts. 
180         Base of airway in hundreds of feet (18,000 feet in this example). 
450         Top of airways in hundreds of feet (45,000 feet in this example). 
J13         Airway segment name.  If multiple airways share this segment, then all names will be included separated by a hyphen (eg. "J13-J14-J15")  

Astronomical:  740 Version (latest)
===================================
from : http://data.x-plane.com/file_specs/Astro740.htm
sample
 6.752569 -16.713143 -1.43 Sirius
19.846301   8.867385  0.77 Altair
 2.529743  89.264138  1.97 Polaris 

6.752569   Right Ascension in decimal hours.  Always a positive number. 
-16.713143 Declination in decimal degrees.  Positive declinations are north of the celestial equator (eg. the pole star, Polaris, is at a declination of 89.264138 degrees). 
-1.43      Visible magnitude of the star.  This is a weird logarithmic scale (low numbers are brightest), and stars to a magnitude of +6.5 are considered visible to the naked eye (though this will vary hugely with your local seeing conditions, light pollution, altitude, etc.).  Sirius (the brightest star in the night sky) has a negative magnitude (-1.43) because it is very, very bright.  
Sirius     Star name (optional - not used by X-Plane).  

Any units of angular measure can be used for right ascension, but it is customarily 
measured in hours ( h ), minutes ( m ), and seconds ( s ), with 24h being equivalent 
to a full circle.

################################
### ICAO.threshold.xml file
<?xml version="1.0"?>
<PropertyList>
  <runway>
    <threshold>
      <lon>-107.548332</lon>
      <lat>53.3667557256793</lat>
      <rwy>16</rwy>
      <hdg-deg>180.00</hdg-deg>
      <displ-m>0</displ-m>
      <stopw-m>0</stopw-m>
    </threshold>
    <threshold>
      <lon>-107.548331146226</lon>
      <lat>53.3598982743207</lat>
      <rwy>34</rwy>
      <hdg-deg>0.00</hdg-deg>
      <displ-m>0</displ-m>
      <stopw-m>0</stopw-m>
    </threshold>
  </runway>
</PropertyList>


EOF
# ================================
