#!/usr/bin/perl
# NAME: findapX.pl
# AIM: Read X-Plane (Robin Pell) apt.dat, and find an airport given the name,
# geoff mclane - http://geoffmclane.com/mperl/index.htm - 20100801
use strict;
use warnings;
use Time::HiRes qw( gettimeofday tv_interval );
unshift(@INC, 'C:/GTools/perl');
require 'logfile.pl' or die "Error: Unable to locate logfile.pl ...\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";
# =============================================================================
# This NEEDS to be adjusted to YOUR particular default location of these files.
my $FGROOT = "D:/SAVES/xplane";
my $APTFILE 	  = "$FGROOT/apt.dat";	# the airports data file
my $NAVFILE 	  = "$FGROOT/earth_nav.dat";	# the NAV, NDB, etc. data file
##my $FIXFILE 	  = "$FGROOT/Navaids/fix.dat.gz";	# the FIX data file
# =============================================================================

# log file stuff
my ($LF);
my $pgmname = $0;
my $outfile = 'temp.'.$pgmname.'.txt';
if ($pgmname =~ /\w{1}:\\.*/) {
	my @tmpsp = split(/\\/,$pgmname);
	$pgmname = $tmpsp[-1];
	$outfile = 'temp.'.$pgmname.'.txt';
}
open_log($outfile);
my $t0 = [gettimeofday];

# 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 = $APTFILE;
my $navdat = $NAVFILE;

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 $lat = 37.6;
my $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

# features
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

# 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;

my ($file_version);

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

# apt.dat.gz CODES - see http://x-plane.org/home/robinp/Apt810.htm for DETAILS
my $aln =     '1';	# airport line
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 @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 = '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);
# ===================

########################################################################
### 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;
}

sub show_airports_found {
	my ($mx) = shift;	# limit the AIRPORT OUTPUT
	my $scnt = $acnt;
	my $tile = '';
	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 [$lat,$lon], using diff [$maxlatd,$maxlond] ...\n" );
	} else {    # $SRCHNAME
		prt( "matching [$aptname] ...\n" );
	}
	for (my $i = 0; $i < $scnt; $i++) {
		$diff = $aptlist2[$i][0];
		$icao = $aptlist2[$i][1];
		$name = $aptlist2[$i][2];
		$alat = $aptlist2[$i][3];
		$alon = $aptlist2[$i][4];
		$tile = get_bucket_info( $alon, $alat );
		while (length($icao) < 4) {
			$icao .= ' ';
		}
		$line = $diff;
		while (length($line) < 6) {
			$line = ' '.$line;
		}
		#$line .= ' '.$icao.' '.$name.' ('.$alat.','.$alon.") tile=$tile";
		$line .= ' '.$icao.' '.$name.' ('.$alat.','.$alon.") tile=".get_tile($alon,$alat);
		prt("$line\n");
		$outcount++;
		add_2_tiles($tile);
	}
	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 ($t) = shift;
    if ($t && length($t)) {
        my $txt = "$t";
        my $cnt = 0;
        foreach my $n (@navset) {
            if ($n eq $txt) {
                $actnav = $navtypes[$cnt];
                return 1;
            }
            $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;
	prt( "For $ac airports, found $nc NAVAIDS, " );
    if ($usekmrange) {
    	prt( "within [$max_range_km] 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++ ) {
			$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);
	}
	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);

    }
}

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" );
    set_apt_version( \@lines, $cnt );
    if ($SRCHICAO) {
        prt( "Searching for ICAO [$apticao] ...\n" );
    } elsif ($SRCHONLL) {
        prt( "Searching for LAT,LON [$lat,$lon], using max. diff. [$maxlatd,$maxlond] ...\n" );
    } else {    # $SRCHNAME (default)
        prt( "Searching for NAME [$aptname] ...\n" );
    }
    my ($rlat1,$rlat2,$rlon1,$rlon2);
    foreach $line (@lines) {
        $line = trimall($line);
        ###prt("$line\n");
        my @arr = split(/\s/,$line);

        if ($line =~ /^$aln\s+/) {	# start with '1'
            if (length($apt) && ($rwycnt > 0)) {
                $alat = $glat / $rwycnt;
                $alon = $glon / $rwycnt;
                $dlat = abs( $lat - $alat );
                $dlon = abs( $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");
                push(@aptlist, [$diff, $icao, $name, $alat, $alon]);
                if ($SRCHICAO) {
                    if ($icao =~ /$apticao/) {
                            prt("$icao, $name, $alat, $alon \n") if ($dbg1);
                            push(@aptlist2, [$diff, $icao, $name, $alat, $alon]);
                    }
                } else {
                    if ($SRCHONLL) {
                        if (($dlat < $maxlatd) && ($dlon < $maxlond)) {
                            prt("$icao, $name, $alat, $alon \n") if ($dbg1);
                            push(@aptlist2, [$diff, $icao, $name, $alat, $alon]);
                        }
                    } else {    # $SRCHNAME (default)
                        if ($name =~ /$aptname/i) {
                            prt("$icao, $name, $alat, $alon \n") if ($dbg1);
                            push(@aptlist2, [$diff, $icao, $name, $alat, $alon]);
                        }
                    }
                }
            }
            $apt = $line;   # set the AIRPORT line
            $rwycnt = 0;    # restart RUNWAY counter
            $glat = 0;
            $glon = 0;
            $totaptcnt++;	# count another AIRPORT
            prt("$apt\n") if ($dbg10);
        } elsif ($line =~ /^$rln\s+/) {
            $rlat1 = $arr[$of_lat1];
            $rlon1 = $arr[$of_lon1];
            $rlat2 = $arr[$of_lat2];
            $rlon2 = $arr[$of_lon2];
            $rlat = ($rlat1 + $rlat2) / 2;
            $rlon = ($rlon1 + $rlon2) / 2;
            ###prt( "$line [$rlat, $rlon]\n" );
            $glat += $rlat;
            $glon += $rlon;
            $rwycnt++;
        } elsif ($line =~ /^$lastln\s?/) {	# 99, followed by space, count 0 or more ...
            prt( "Reached END OF FILE ... \n" ) if ($dbg1);
            last;
        }
    }

    # do any LAST entry
    if ($rwycnt > 0) {
        $alat = $glat / $rwycnt;
        $alon = $glon / $rwycnt;
        $dlat = abs( $lat - $alat );
        $dlon = abs( $lon - $alon );
        $diff = int( ($dlat * 10) + ($dlon * 10) );
        my @arr2 = split(/ /,$apt);
        $icao = $arr2[4];
        $name = join(' ', splice(@arr2,5));
        ###prt("$diff [$apt] (with $rwycnt runways at [$alat, $alon]) ...\n");
        ###prt("$diff [$icao] [$name] ...\n");
        push(@aptlist, [$diff, $icao, $name, $alat, $alon]);
        if ($SRCHICAO) {
            if ($name =~ /$apticao/) {
                    prt("$icao, $name, $alat, $alon \n") if ($dbg1);
                    push(@aptlist2, [$diff, $icao, $name, $alat, $alon]);
            }
        } else {
            if ($SRCHONLL) {
                if (($dlat < $maxlatd) && ($dlon < $maxlond)) {
                    prt("$icao, $name, $alat, $alon \n") if ($dbg1);
                    push(@aptlist2, [$diff, $icao, $name, $alat, $alon]);
                }
            } else {
                if ($name =~ /$aptname/i) {
                    prt("$icao, $name, $alat, $alon \n") if ($dbg1);
                    push(@aptlist2, [$diff, $icao, $name, $alat, $alon]);
                }
            }
        }
    }
}

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);
    my @nav_lines = load_nav_file();
	my $nav_cnt = scalar @nav_lines;
	my $ac = scalar @aptlist2;
	prt("Processing $nav_cnt lines ...\n");
    if ($ac == 1) {
   		$alat = $aptlist2[0][3];
		$alon = $aptlist2[0][4];
        if ($usekmrange) {
            prt("Using distance of [$max_range_km] 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 [$max_range_km] 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+/) {
            my $ind = index($line,',');
            prt( "NAVAID: ".substr($line, 0, (($ind > 0) ? $ind : 50) )."\n" );   # 810 Version - DAFIF ...
        } 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) {
                prt("Expanded to [$max_range_km] Km from $ac airport(s)...\n" );
            } else {
                prt("Expanded to [$nmaxlatd,$nmaxlond] from $ac airport(s)...\n" );
            }
            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) {
            prt("using distance $max_range_km Km...\n" );
        } else {
            prt("using difference $nmaxlatd, $nmaxlond...\n" );
        }
        $nmaxlatd = $def_latd;
        $nmaxlond = $def_lond;
        $max_range_km = $def_dist;
    }
	prt("Done - Found $navs_found nearby NAVAIDS, of $vcnt searched...\n" );
}

# put least first
sub mycmp_ascend_n4 {
   if (${$a}[4] < ${$b}[4]) {
      return -1;
   }
   if (${$a}[4] > ${$b}[4]) {
      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;
}

##############
### 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;
}


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=$lat,$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( " -aptdata=$aptdat - Use a specific AIRPORT data file.\n" );
	prt( " -navdata=$navdat - Use a specific NAVAID data file.\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" );
	mydie( "                                     Happy Searching.\n" );
}

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

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

sub show_warnings {
	my ($dbg) = shift;
    if (@warnings) {
        prt( "\nGot ".scalar @warnings." WARNINGS ...\n" );
        foreach my $line (@warnings) {
            prt("$line\n" );
        }
        prt("\n");
    } elsif ($dbg) {
        prt("\nNo warnings issued.\n\n");
    }
}

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

############################################
### 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 $lat, $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);

############################################################

# set $SRCHICAO on/off
# set $SRCHONLL on/off
sub parse_args {
	my (@av) = @_;
	my (@arr);
	while(@av) {
		my $arg = $av[0]; # shift @av;
        my $lcarg = lc($arg);
		if ($arg =~ /\?/) {
			give_help();

        # BY ICAO
		} elsif ( $arg =~ /-icao=(.+)/i ) {
			$apticao = $1;
			$SRCHICAO = 1;
			$SRCHONLL = 0;
			$SRCHNAME = 0;
			prt( "Search using ICAO of [$apticao] ...\n" );
		} elsif ( $lcarg eq '-icao' ) {
			require_arg(@av);
			shift @av;
			$SRCHICAO = 1;
			$SRCHONLL = 0;
			$SRCHNAME = 0;
			$apticao = $av[0];
			prt( "Search using ICAO of [$apticao] ...\n" );

        # BY LAT,LON
		} elsif ( $arg =~ /-latlon=(.+)/i ) {
			$SRCHICAO = 0;
			$SRCHONLL = 1;
			$SRCHNAME = 0;
			@arr = split(',', $1);
			if (scalar @arr == 2) {
				$lat = $arr[0];
				$lon = $arr[1];
				prt( "Search using LAT,LON of [$lat,$lon] ...\n" );
			} 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) {
				$lat = $arr[0];
				$lon = $arr[1];
				prt( "Search using LAT,LON of [$lat,$lon] ...\n" );
			} 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( "Search using NAME of [$aptname] ...\n" );
		} elsif ( $lcarg eq '-name' ) {
			require_arg(@av);
			shift @av;
			$SRCHICAO = 0;
			$SRCHONLL = 0;
			$SRCHNAME = 1;
			$aptname = $av[0];
			prt( "Search using NAME of [$aptname] ...\n" );
		} elsif ( $arg =~ /^-loadlog$/i ) {
			$loadlog = 1;
			prt( "Load log into wordpad.\n" );
		} elsif ( $arg =~ /^-shownavs$/i ) {
			$SHOWNAVS = 1;
			prt( "And show NAVAIDS around airport, if any.\n" );
		} elsif ( $arg =~ /^-s$/i ) {
			$SHOWNAVS = 1;
			prt( "And show NAVAIDS around airport, if any.\n" );
		} elsif ( $arg =~ /-maxll=(.+)/i ) {
			@arr = split(',', $1);
			if (scalar @arr == 2) {
				$maxlatd = $arr[0];
				$maxlond = $arr[1];
				prt( "Search maximum difference LAT,LON of [$maxlatd,$maxlond] ...\n" );
			} 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( "Search maximum difference LAT,LON of [$maxlatd,$maxlond] ...\n" );
			} 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( "Search maximum difference LAT,LON of [$nmaxlatd,$nmaxlond] ...\n" );
			} 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( "Search maximum difference LAT,LON of [$nmaxlatd,$nmaxlond] ...\n" );
			} else {
				mydie( "ERROR: Failed to find maximum lat,lon difference in [$arg]...\n" );
			}
		} elsif ( $arg =~ /-aptdata=(.+)/i ) {
			$aptdat = $1;	# the airports data file
			prt( "Using AIRPORT data file [$aptdat] ...\n" );
		} elsif ( $lcarg eq '-aptdata' ) {
			require_arg(@av);
			shift @av;
			$aptdat = $av[0];	# the airports data file
			prt( "Using AIRPORT data file [$aptdat] ...\n" );
		} elsif ( $arg =~ /-navdata=(.+)/i ) {
			$navdat = $1;
			prt( "Using NAVAID data file [$navdat] ...\n" );
		} elsif ( $lcarg eq '-navdata' ) {
			require_arg(@av);
			shift @av;
			$navdat = $av[0];
			prt( "Using NAVAID data file [$navdat] ...\n" );
		} elsif ( $arg =~ /-maxout=(.+)/i ) {
			$max_cnt = $1;
			prt( "Airport output limited to $max_cnt. A zero (0), for no limit\n" );
		} elsif ( $lcarg eq '-maxout' ) {
			require_arg(@av);
			shift @av;
			$max_cnt = $av[0];
			prt( "Airport output limited to $max_cnt. A zero (0), for no limit\n" );
		} elsif ( $arg =~ /-range=(.+)/i ) {
			$max_range_km = $1;
			prt( "Navaid search using $max_range_km Km. A zero (0), for no limit\n" );
            $usekmrange = 1;
			prt( "Navaid search using $max_range_km Km.\n" );
		} elsif ( $lcarg eq '-range' ) {
			require_arg(@av);
			shift @av;
			$max_range_km = $av[0];
			prt( "Navaid search using $max_range_km Km. A zero (0), for no limit\n" );
            $usekmrange = 1;
			prt( "Navaid search using $max_range_km Km.\n" );
        } elsif ( $lcarg eq '-r' ) {
            $usekmrange = 1;
			prt( "Navaid search using $max_range_km Km.\n" );
        } elsif (( $lcarg eq '-tryhard' )||( $lcarg eq '-t' )) {
            $tryharder = 1;  # Expand the search for NAVAID, until at least 1 found
			prt( "Navaid search 'tryharder' set.\n" );
		} else {
			if (substr($arg,0,1) eq '-') {
				mydie( "ERROR: Unknown argument [$arg]. Try ? for HELP ...\n" );
			} else {
				# ASSUME AN AIRPORT NAME
				$SRCHICAO = 0;
				$SRCHONLL = 0;
				$SRCHNAME = 1;
				$aptname = $av[0];
				prt( "Search using NAME of [$aptname] ...\n" );
			}
		}
		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;
	} elsif ($test_ll) {
		$lat = $def_lat;
		$lon = $def_lon;
		$maxlatd = 0.1;
		$maxlond = 0.1;
		$SRCHICAO = 0;
		$SRCHONLL = 1;
		$SRCHNAME = 0;
	} 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;
	}

	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/file_specs/XP%20APT850%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 Zero, one or many for each airport
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
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

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