#!/usr/bin/perl -w
# NAME: aptextract.pl
# AIM: Given an ICAO, load apt.dat.gz, and extract that airport to the out file
# 07/12/2014 geoff mclane http://geoffair.net/mperl
use strict;
use warnings;
use File::Basename;  # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] )
use Time::HiRes qw( gettimeofday tv_interval );
use Cwd;
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";
# 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);

# user variables
my $VERS = "0.0.4 2014-12-25";
###my $VERS = "0.0.3 2014-12-07";
my $load_log = 0;
my $in_icao = '';
my $verbosity = 0;
my $out_file = '';
my $fgroot = "X:\\fgdata";
my $aptdat = "X:\\fgdata\\Airports\\apt.dat.gz";
my $cmpdat = '';
my $writesigns = 0;
my $xg_out = '';
my $add_parkpos = 0;
my $def_rad = 30;
my $add_names = 1;
my $at_head   = 0;
my $add_info  = 0;
my $box_color = 'blue';
my $hdg_color = 'white';

# ### DEBUG ###
my $debug_on = 0;
# from : http://gateway.x-plane.com/artist/page/193
# EDFB_Scenery_Pack.zip
# EDFH_Scenery_Pack.zip
# EDFL_Scenery_Pack.zip
# EDFQ_Scenery_Pack.zip
# from : http://gateway.x-plane.com/artist/page/25
# ZSPD_Scenery_Pack.zip
# 08R_Scenery_Pack.zip
#my $def_file = 'EDFB';
#my $def_cmp = 'C:\Users\user\Downloads\EDFB_Scenery_Pack\Earth nav data\apt.dat';
#my $def_file = 'EDFH';
#my $def_cmp = 'C:\Users\user\Downloads\EDFH_Scenery_Pack\Earth nav data\apt.dat';
#my $def_file = 'EDFL';
#my $def_cmp = 'C:\Users\user\Downloads\EDFL_Scenery_Pack\Earth nav data\apt.dat';
#my $def_file = 'EDFQ';
#my $def_cmp = 'C:\Users\user\Downloads\EDFQ_Scenery_Pack\Earth nav data\apt.dat';
#my $def_file = 'ZSPD';
#my $def_cmp = 'D:\FG\xplane\ZSPD_Scenery_Pack\Earth nav data\apt.dat';
#my $def_file = '08R';
#my $def_cmp = 'C:\Users\user\Downloads\08R_Scenery_Pack\Earth nav data\apt.dat';
my $def_file = 'KSFO';
my $def_cmp = 'D:\FG\xplane\KSFO_Scenery_Pack\Earth nav data\apt.dat';

### program variables
my @warnings = ();
my $cwd = cwd();
my ($bgntm);
my @apt_lines = ();
my @my_apt = ();

# constants
#/** Feet to Meters */
my $FEET_TO_METER = 0.3048;

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

sub process_in_file($) {
    my ($inf) = @_;
    if (! open INF, "<$inf") {
        pgm_exit(1,"ERROR: Unable to open file [$inf]\n"); 
    }
    my @lines = <INF>;
    close INF;
    my $lncnt = scalar @lines;
    prt("Processing $lncnt lines, from [$inf]...\n");
    my ($line,$inc,$lnn);
    $lnn = 0;
    foreach $line (@lines) {
        chomp $line;
        $lnn++;
        if ($line =~ /\s*#\s*include\s+(.+)$/) {
            $inc = $1;
            prt("$lnn: $inc\n");
        }
    }
}

sub load_apt_dat() {
    $bgntm = [gettimeofday];
    if ($aptdat =~ /\.gz$/i) {
        if (!open(INF,"gzip -cdq $aptdat|")) {
            pgm_exit(1,"Failed to 'open' file '$aptdat'!\n");
        }
    } else {
        if (!open( INF, "<$aptdat") ) {
            pgm_exit(1,"Error: Unable to open $aptdat\n");
        }
    }
    @apt_lines = <INF>;
    close INF;
    my $cnt = scalar @apt_lines;
    my $elap = secs_HHMMSS( tv_interval( $bgntm, [gettimeofday] ) );
    prt("Loaded $cnt lines from $aptdat... in $elap\n");
}

sub init_bounds($) {
    my $ra = shift;
    push(@{$ra},400);   # 0 min_lon
    push(@{$ra},400);   # 1 min_lat
    push(@{$ra},-400);  # 2 max_lon
    push(@{$ra},-400);  # 3 max_lat
}

sub set_bounds($$$) {
    my ($ra,$lat,$lon) = @_;
    ${$ra}[0] = $lon if ($lon < ${$ra}[0]);
    ${$ra}[1] = $lat if ($lat < ${$ra}[1]);
    ${$ra}[2] = $lon if ($lon > ${$ra}[2]);
    ${$ra}[3] = $lat if ($lat > ${$ra}[3]);
}

sub valid_bounds($) {
    return 1;
}

sub get_bounds($) {
    my $ra = shift;
    my $xg = "color gray\n";
    $xg .= "${$ra}[0] ${$ra}[1]\n"; # min_lon min_lat
    $xg .= "${$ra}[0] ${$ra}[3]\n"; # min_lon max_lat
    $xg .= "${$ra}[2] ${$ra}[3]\n"; # max_lon max_lat
    $xg .= "${$ra}[2] ${$ra}[1]\n"; # max_lon min_lat
    $xg .= "${$ra}[0] ${$ra}[1]\n"; # min_lon min_lat
    $xg .= "NEXT\n";
    return $xg;
}

my $typ100 = 0; # Runway
my $typ101 = 1; # Water runway
my $typ102 = 2; # Helipad
my $typ110 = 3; # Pavement (taxiway or ramp) header Must form a closed loop
my $typ120 = 4; # Linear feature (painted line or light string) header Can form closed loop or simple string
my $typ130 = 5; # Airport boundary header Must form a closed loop
my $typ111 = 6; # Node All nodes can also include a “style” (line or lights)
my $typ112 = 7; # Node with Bezier control point Bezier control points define smooth curves
my $typ113 = 8; # Node with implicit close of loop Implied join to first node in chain
my $typ114 = 9; # Node with Bezier control point, with implicit close of loop Implied join to first node in chain
my $typ115 = 10; # Node terminating a string (no close loop) No “styles” used
my $typ116 = 11; # Node with Bezier control point, terminating a string (no close loop) No “styles” used
my $typ14  = 12; # Airport viewpoint One or none for each airport
my $typ15  = 13; # Aeroplane startup location *** Convert these to new row code 1300 ***
my $typ18  = 14; # Airport light beacon One or none for each airport
my $typ19  = 15; # Windsock Zero, one or many for each airport
my $typ20  = 16; # Taxiway sign (inc. runway distance-remaining signs) Zero, one or many for each airport
my $typ21  = 17; # Lighting object (VASI, PAPI, Wig-Wag, etc.) Zero, one or many for each airport
my $typ1000 = 18; # Airport traffic flow Zero, one or many for an airport. Used if following rules met
# (rules of same type use ‘or’ logic, rules of a different type use
# ‘and’ logic). First flow to pass all rules is used.
my $typ1001 = 19;   # Traffic flow wind rule Zero, one or many for a flow. Multiple rules use ‘or’ logic.
my $typ1002 = 20;   # Traffic flow minimum ceiling rule Zero or one rule for each flow
my $typ1003 = 21;   # Traffic flow minimum visibility rule Zero or one rule for each flow
my $typ1004 = 22;   # Traffic flow time rule Zero, one or many for a flow. Multiple rules use ‘or’ logic.
my $typ1100 = 23;   # Runway-in-use arrival/departure constraints First constraint met is used. Sequence matters!
my $typ1101 = 24;   # VFR traffic pattern Zero or one pattern for each traffic flow
my $typ1200 = 25;   # Header indicating that taxi route network data follows
my $typ1201 = 26;   # Taxi route network node Sequencing is arbitrary. Must be part of one or more edges.
my $typ1202 = 27;   # Taxi route network edge Must connect two nodes
my $typ1204 = 28;   # Taxi route edge active zone Can refer to up to 4 runway ends
my $typ1300 = 29;   # Airport location (deprecates code 15) Not explicitly connected to taxi route network
# 50 – 56 Communication frequencies Zero, one or many for each airport
my $typ50   = 30;
my $typ51   = 31;
my $typ52   = 32;
my $typ53   = 33;
my $typ54   = 34;
my $typ55   = 35;
my $typ56   = 36;
my $typ1    = 37;
my $typ16   = 38;
my $typ17   = 39;
my $typ10   = 40;

my $typmax  = 41;

my %typ_text = (
    $typ100 => '100  Runways',
    $typ101 => '101  Water runways',
    $typ102 => '102  Helipads',
    $typ110 => '110  Pavement (taxiway or ramp) header closed loop',
    $typ120 => '120  Linear feature', # (painted line or light string) header Can form closed loop or simple string
    $typ130 => '130  Airport boundary header',   # Must form a closed loop
    $typ111 => '111  Node style',    # All nodes can also include a “style” (line or lights)
    $typ112 => '112  Node with Bezier',  # control point Bezier control points define smooth curves
    $typ113 => '113  Node with implicit close of loop',  # Implied join to first node in chain
    $typ114 => '114  Node with Bezier close',    # control point, with implicit close of loop Implied join to first node in chain
    $typ115 => '115  Node terminating a string', # (no close loop) No “styles” used
    $typ116 => '116  Node with Bezier control point', # terminating a string (no close loop) No “styles” used
    $typ14  => '14   Airport viewpoint', # One or none for each airport
    $typ15  => '15   Startup location',  # *** Convert these to new row code 1300 ***
    $typ18  => '18   Light beacon',  # One or none for each airport
    $typ19  => '19   Windsocks', # Zero, one or many for each airport
    $typ20  => '20   Taxiway sign',  # (inc. runway distance-remaining signs) Zero, one or many for each airport
    $typ21  => '21   Lighting objects',  # (VASI, PAPI, Wig-Wag, etc.) Zero, one or many for each airport
    $typ1000 => '1000 Traffic flow', # Zero, one or many for an airport. Used if following rules met
    $typ1001 => '1001 Traffic flow wind',    # rule Zero, one or many for a flow. Multiple rules use ‘or’ logic.
    $typ1002 => '1002 Traffic flow minimum ceiling', # rule Zero or one rule for each flow
    $typ1003 => '1003 Traffic flow minimum visibility',  # rule Zero or one rule for each flow
    $typ1004 => '1004 Traffic flow time',    # rule Zero, one or many for a flow. Multiple rules use ‘or’ logic.
    $typ1100 => '1100 Runway-in-use',    # arrival/departure constraints First constraint met is used. Sequence matters!
    $typ1101 => '1101 VFR traffic pattern',  # Zero or one pattern for each traffic flow
    $typ1200 => '1200 Taxi route',   # Header indicating that taxi route network data follows
    $typ1201 => '1201 Taxi route network',   # node Sequencing is arbitrary. Must be part of one or more edges.
    $typ1202 => '1202 Taxi route network edge',  # Must connect two nodes
    $typ1204 => '1204 Taxi route edge active',   # zone Can refer to up to 4 runway ends
    $typ1300 => '1300 Airport location', # (deprecates code 15) Not explicitly connected to taxi route network
    $typ50   => '50   ATIS',
    $typ51   => '51   Unicom',
    $typ52   => '52   CLD',
    $typ53   => '53   GND',
    $typ54   => '54   TWR',
    $typ55   => '55   APP',
    $typ56   => '56   DEP',
    $typ1    => '1    Airport',
    $typ16   => '16   Water-port',
    $typ17   => '17   Heliport',
    $typ10   => '10   Runways810',
    $typmax  => '0    LAST'
    );

sub set_type_count($$$) {
    my ($type,$rtc,$line) = @_;
    my $index = -1;
    if ($type == 100) {
        $index = $typ100;
    } elsif ($type == 101) {
        $index = $typ101;
    } elsif ($type == 102) {
        $index = $typ102;
    } elsif ($type == 110) {
        $index = $typ110;
    } elsif ($type == 120) {
        $index = $typ120;
    } elsif ($type == 130) {
        $index = $typ130;
    } elsif ($type == 111) {
        $index = $typ111;
    } elsif ($type == 112) {
        $index = $typ112;
    } elsif ($type == 113) {
        $index = $typ113;
    } elsif ($type == 114) {
        $index = $typ114;
    } elsif ($type == 115) {
        $index = $typ115;
    } elsif ($type == 116) {
        $index = $typ116;
    } elsif ($type == 14) {
        $index = $typ14;
    } elsif ($type == 15) {
        $index = $typ15;
    } elsif ($type == 18) {
        $index = $typ18;
    } elsif ($type == 19) {
        $index = $typ19;
    } elsif ($type == 20) {
        $index = $typ20;
    } elsif ($type == 21) {
        $index = $typ21;
    } elsif ($type == 1000) {
        $index = $typ1000;
    } elsif ($type == 1001) {
        $index = $typ1001;
    } elsif ($type == 1002) {
        $index = $typ1002;
    } elsif ($type == 1003) {
        $index = $typ1003;
    } elsif ($type == 1004) {
        $index = $typ1004;
    } elsif ($type == 1100) {
        $index = $typ1100;
    } elsif ($type == 1101) {
        $index = $typ1101;
    } elsif ($type == 1200) {
        $index = $typ1200;
    } elsif ($type == 1201) {
        $index = $typ1201;
    } elsif ($type == 1202) {
        $index = $typ1202;
    } elsif ($type == 1204) {
        $index = $typ1204;
    } elsif ($type == 1300) {
        $index = $typ1300;
    } elsif ($type == 50) {
        $index = $typ50;
    } elsif ($type == 51) {
        $index = $typ51;
    } elsif ($type == 52) {
        $index = $typ52;
    } elsif ($type == 53) {
        $index = $typ53;
    } elsif ($type == 54) {
        $index = $typ54;
    } elsif ($type == 55) {
        $index = $typ55;
    } elsif ($type == 56) {
        $index = $typ56;
    } elsif ($type == 1) {
        $index = $typ1;
    } elsif ($type == 16) {
        $index = $typ16;
    } elsif ($type == 17) {
        $index = $typ17;
    } elsif ($type == 10) {
        $index = $typ10;
    } else {
        pgm_exit(1,"Unprocessed type $type\nline: $line\n*** FIX ME ***\n");
    }
    ${$rtc}[$index]++;
    return $index;
}

sub rwy_xg_stg($$$$$$) {
    my ($elat1,$elon1,$elat2,$elon2,$widm,$colr) = @_;
    my $hwidm = $widm / 2;
    my ($az1,$az2,$s,$az3,$az4);
    my ($lon1,$lon2,$lon3,$lon4,$lat1,$lat2,$lat3,$lat4);
    my $xg = "color $colr\n";
    my $res = fg_geo_inverse_wgs_84($elat1,$elon1,$elat2,$elon2,\$az1,\$az2,\$s);
    $az3 = $az1 + 90;
    $az3 -= 360 if ($az3 >= 360);
    $az4 = $az1 - 90;
    $az4 += 360 if ($az4 < 0);
    $res = fg_geo_direct_wgs_84($elat1,$elon1, $az3, $hwidm, \$lat1, \$lon1, \$az2);
    $xg .= "$lon1 $lat1\n";
    $res = fg_geo_direct_wgs_84($elat1,$elon1, $az4, $hwidm, \$lat2, \$lon2, \$az2);
    $xg .= "$lon2 $lat2\n";
    $res = fg_geo_direct_wgs_84($elat2,$elon2, $az4, $hwidm, \$lat3, \$lon3, \$az2);
    $xg .= "$lon3 $lat3\n";
    $res = fg_geo_direct_wgs_84($elat2,$elon2, $az3, $hwidm, \$lat4, \$lon4, \$az2);
    $xg .= "$lon4 $lat4\n";
    $xg .= "$lon1 $lat1\n";
    $xg .= "NEXT\n";
    #### prt($xg);
    return $xg;
}

sub trim_sign($) {
    my $txt = shift;
    my $sign = '';
    my $len = length($txt);
    my ($i,$ch);
    for ($i = 0; $i < $len; $i++) {
        $ch = substr($txt,$i,1);
        if ($ch eq '{') {
            $i++;
            for (; $i < $len; $i++) {
                $ch = substr($txt,$i,1);
                last if ($ch eq '}');
            }
        } else {
            if ($ch eq '_') {
                $sign .= ' ';
            } else {
                $sign .= $ch;
            }
        }
    }
    return $sign;
}

sub get_rectange($$$$$) {
    my ($clat,$clon,$hdg,$rad,$name) = @_;
    my ($res);
    my ($lat1, $lon1, $az1);
    my ($lat2, $lon2, $az2);
    my $hrad = $rad / 2.0;
    my $rhdg = $hdg + 180.0;
    if ($rhdg >= 360.0) {
        $rhdg -= 360.0;
    }
    # // get tow ends using heading and reciprocal
    $res = fg_geo_direct_wgs_84( $clat, $clon,  $hdg, $hrad, \$lat1, \$lon1, \$az1 );
    $res = fg_geo_direct_wgs_84( $clat, $clon, $rhdg, $hrad, \$lat2, \$lon2, \$az2 );
    #// setup heading to 'right' and 'left'
    my $hdg1 = $hdg + 90.0;
    if ($hdg1 >= 360.0) {
        $hdg1 -= 360.0;
    }
    my $hdg2 = $hdg - 90.0;
    if ($hdg2 < 0.0) {
        $hdg2 += 360.0;
    }
    my ($lata, $lona, $aza);
    my ($latb, $lonb, $azb);
    my ($latc, $lonc, $azc);
    my ($latd, $lond, $azd);
    #// generate the four corners
    $res = fg_geo_direct_wgs_84( $lat1, $lon1, $hdg1, $hrad, \$lata, \$lona, \$aza );
    $res = fg_geo_direct_wgs_84( $lat1, $lon1, $hdg2, $hrad, \$latb, \$lonb, \$azb );
    $res = fg_geo_direct_wgs_84( $lat2, $lon2, $hdg1, $hrad, \$latd, \$lond, \$azd );
    $res = fg_geo_direct_wgs_84( $lat2, $lon2, $hdg2, $hrad, \$latc, \$lonc, \$azc );

#if 0 // 000000000000000000000000000000000000000000000000000000
#    // fill out the boundary
#    add_bbox( pbb, lata, lona );
#    add_bbox( pbb, latb, lonb );
#    add_bbox( pbb, latc, lonc );
#    add_bbox( pbb, latd, lond );
#    add_bbox( pbb, lat1, lon1 );
#    add_bbox( pbb, lat2, lon2 );
#endif // 00000000000000000000000000000000000000000000000000000

    #// generate the Xgraph stream
    my $xg = '';
    #// *******************************************************************
    #// seems default precision is quite small - with very BAD results
    #xg << std::setprecision(def_precision);
    #// *******************************************************************
    if ($name && $add_names && length($name)) {
        if ($at_head) {
            $xg .= "anno $lon1 $lat1";
        } else {
            $xg .= "anno $clon $clat";
        }
        $xg .= " $name";
        if ($add_info) {
            $xg .= " r=$rad h=$hdg";
        }
        $xg .= "\n";
    }
    $xg .= "color $box_color\n";
    $xg .= "$lona $lata\n";
    $xg .= "$lonb $latb\n";
    $xg .= "$lonc $latc\n";
    $xg .= "$lond $latd\n";
    $xg .= "$lona $lata\n";
    $xg .= "NEXT\n";
    $xg .= "color $hdg_color\n";
    $xg .= "$lon1 $lat1\n";
    $xg .= "$lon2 $lat2\n";
    $xg .= "NEXT\n";
    return $xg;
}


sub write_xg_file($) {
    my $ara = shift;    # \@my_apt;
    my $max = scalar @{$ara};
    my ($i,$ra,$typ,$alat,$alon,$icao,$name,$aalt);
    my ($elat1,$elon1,$elat2,$elon2,$az1,$az2,$s);
    my ($rwid,$surf,$rwy1,$rwy2,$res,$line,$cnt);
    my ($rlat,$rlon,$hdg,$hdgr,$eaz1,$eaz2,$colr,$rlen);
    my ($bgn,$end,$dir,$uid,$rra,$ptyp);
    my ($ralen,$j);
    my $gotvp = 0;
    my $gotnm = 0;
    my $xg = "# Generated by $pgmname on ".lu_get_YYYYMMDD_hhmmss_UTC(time())." UTC\n";
    my %routing = ();
    my $glat = 0;
    my $glon = 0;
    my $rwycnt = 0;
    my @bounds = ();
    init_bounds(\@bounds);
    $typ = 0;
    for ($i = 0; $i < $max; $i++) {
        $ra = ${$ara}[$i];
        $ptyp = $typ;       # previous 'type'
        $typ = ${$ra}[0];
        if (($typ == 1)||($typ == 16)||($typ == 17)) {
            $aalt = ${$ra}[1]; # Airport (general) ALTITUDE AMSL
            $icao = ${$ra}[4];
            $name = join(' ', splice(@{$ra},5)); # Name
            $gotnm = 1;
        } elsif ($typ == 14) {   # Airport viewpoint One or none for each airport
            if ($gotvp) {
                prtw("WARNING: Got VP $alat,$alon, NOW ".${$ra}[1].",".${$ra}[2]."?\n");
            }
            $alat = ${$ra}[1];  # 47.53900921   Latitude of viewport in decimal degrees Eight decimal places supported
            $alon = ${$ra}[2];  # -122.30868700 Longitude of viewport in decimal degrees Eight decimal places supported
            set_bounds(\@bounds,$alat,$alon);
            $gotvp = 1;
        } elsif ($typ == 1201) {
            # 1201 Taxi routing node All nodes must be used in at least one edge
            # 1201  31.15839433  121.81188494 both 0 N_start
            $elat1 = ${$ra}[1];
            $elon1 = ${$ra}[2];
            set_bounds(\@bounds,$elat1,$elon1);
            $dir   = ${$ra}[3];
            $uid   = ${$ra}[4];
            if (defined $routing{$uid}) {
                prtw("WARNING: 1201 not unique ID $uid\n");
            }
            $routing{$uid} = [$elat1,$elon1,$dir];
        } elsif ($typ == 10) {
            $rlat = ${$ra}[1];
            $rlon = ${$ra}[2];
            $rwy1 = ${$ra}[3];
            next if ($rwy1 eq 'xxx');
            $rwycnt++;
            $glat += $rlat;
            $glon += $rlon;
        } elsif ($typ == 100) {
            $elat1 = ${$ra}[9];
            $elon1 = ${$ra}[10];
            $elat2 = ${$ra}[18];
            $elon2 = ${$ra}[19];
            $rwycnt++;
            $glat += ($elat1 + $elon1) / 2;
            $glon += ($elon1 + $elon2) / 2;
        }
    }
    if ($gotvp) {
        prt("Got airport viewport at $alat,$alon\n");
    } elsif ($rwycnt) {
        $alat = $glat / $rwycnt;
        $alon = $glon / $rwycnt;
        $gotvp = 1;
        prt("Got airport av rwy ctr at $alat,$alon\n");
    } else {
        prtw("WARNING: No viewport and no runways (10,100)! TODO: Use something else!\n");
    }
    for ($i = 0; $i < $max; $i++) {
        $ra = ${$ara}[$i];
        $ralen = scalar @{$ra};
        $typ = ${$ra}[0];
        if (($typ == 1)||($typ == 16)||($typ == 17)) {
            $aalt = ${$ra}[1]; # Airport (general) ALTITUDE AMSL
            $icao = ${$ra}[4];
            $name = join(' ', splice(@{$ra},5)); # Name
            if ($gotvp) {
                $line = "anno $alon $alat $icao $aalt $name\n";
                $xg .= $line;
                prt($line);
            }
        } elsif ($typ == 10) {
            # previous 810 type runway and taxi way
            # 0   1          2          3   4       5
            # 10  36.962213  127.031071 14x 131.52  8208 1595.0620 0000.0000   150 321321  1 0 3 0.25 0 0300.0300
            # 10  36.969145  127.020106 xxx 221.51   329 0.0 0.0    75 161161  1 0 0 0.25 0 
            $rlat = ${$ra}[1];
            $rlon = ${$ra}[2];
            $rwy1 = ${$ra}[3];
            $hdg  = ${$ra}[4];
            $rlen = (${$ra}[5] * $FEET_TO_METER);  # length, in feet to meters
            $rwid = (${$ra}[8] * $FEET_TO_METER);  # Runway or taxiway segment width in feet to meters
            $hdgr = $hdg + 180;
            $hdgr -= 360 if ($hdgr >= 360);
            $res = fg_geo_direct_wgs_84( $rlat, $rlon, $hdg , ($rlen / 2), \$elat1, \$elon1, \$eaz1 );
            $res = fg_geo_direct_wgs_84( $rlat, $rlon, $hdgr, ($rlen / 2), \$elat2, \$elon2, \$eaz2 );
            $colr = 'red';
            if ($rwy1 eq 'xxx') {
                $colr = 'gray';
            }
            # =======================================================
            $xg .= rwy_xg_stg($elat1,$elon1,$elat2,$elon2,$rwid,$colr);
            # =======================================================
            set_bounds(\@bounds,$elat1,$elon1);
            set_bounds(\@bounds,$elat2,$elon2);

        } elsif ($typ == 100) {
            $rwid  = ${$ra}[1];  # WIDTH in meters? NOT SHOWN
            $surf  = ${$ra}[2];  # add surface type
            $rwy1  = ${$ra}[8];
            $elat1 = ${$ra}[9];
            $elon1 = ${$ra}[10];
            $rwy2 = ${$ra}[17];
            $elat2 = ${$ra}[18];
            $elon2 = ${$ra}[19];
            ###$res = fg_geo_inverse_wgs_84 ($elat1,$elon1,$elat2,$elon2,\$az1,\$az2,\$s);
            # =======================================================
            $xg .= rwy_xg_stg($elat1,$elon1,$elat2,$elon2,$rwid,'red');
            # =======================================================
            set_bounds(\@bounds,$elat1,$elon1);
            set_bounds(\@bounds,$elat2,$elon2);
        } elsif ($typ == 130) {
            $i++;
            # 47.53752190    [All nodes] Latitude of node in decimal degrees Eight decimal places supported
            # -122.30826710  [All nodes] Longitude of node in decimal degrees Eight decimal places supported
            $xg .= "color green\n";
            $cnt = 0;
            for (; $i < $max; $i++) {
                $ra = ${$ara}[$i];
                $typ = ${$ra}[0];
                $elat1 = ${$ra}[1];
                $elon1 = ${$ra}[2];
                set_bounds(\@bounds,$elat1,$elon1);
                if ($cnt == 0) {
                    $elat2 = $elat1;
                    $elon2 = $elon1;
                }
                if ($typ == 111) {
                    $xg .= "$elon1 $elat1\n";
                } elsif ($typ == 113) {
                    $xg .= "$elon1 $elat1\n";
                    last;
                } elsif (($typ == 112)||($typ == 114)||($typ == 116)) {
                    #    47.53757385  [112, 114, 116 only] Latitude of Bezier control point in decimal degrees Eight decimal places supported. Ignore for 111, 113, 115
                    #  -122.30824831  [112, 114, 116 only] Latitude of Bezier control point in decimal degrees Eight decimal places supported. Ignore for 111, 113, 115
                    $xg .= "$elon1 $elat1\n";
                    $elat1 = ${$ra}[3];
                    $elon1 = ${$ra}[4];
                    $xg .= "$elon1 $elat1\n";
                    set_bounds(\@bounds,$elat1,$elon1);
                    last if ($typ == 114);
                } else {
                    pgm_exit(1,"Type $typ: Other than 111 or 113 in airport bounds! *** FIX ME ***\n");
                }
                $cnt++;
            }
            $xg .= "$elon2 $elat2\n";
            $xg .= "NEXT\n";
        } elsif ($typ == 110) {
            $i++;
            $xg .= "color gray\n";
            $cnt = 0;
            for (; $i < $max; $i++) {
                $ra = ${$ara}[$i];
                $typ = ${$ra}[0];
                $elat1 = ${$ra}[1];
                $elon1 = ${$ra}[2];
                set_bounds(\@bounds,$elat1,$elon1);
                if ($cnt == 0) {
                    $elat2 = $elat1;
                    $elon2 = $elon1;
                }
                if ($typ == 130) {
                    $i--;   # back up to this
                    last;
                }
                if ($typ == 111) {
                    $xg .= "$elon1 $elat1\n";
                } elsif (($typ == 112)||($typ == 114)||($typ == 116)) {
                    #    47.53757385  [112, 114, 116 only] Latitude of Bezier control point in decimal degrees Eight decimal places supported. Ignore for 111, 113, 115
                    #  -122.30824831  [112, 114, 116 only] Latitude of Bezier control point in decimal degrees Eight decimal places supported. Ignore for 111, 113, 115
                    $xg .= "$elon1 $elat1\n";
                    $elat1 = ${$ra}[3];
                    $elon1 = ${$ra}[4];
                    $xg .= "$elon1 $elat1\n";
                    set_bounds(\@bounds,$elat1,$elon1);
                    last if ($typ == 114);
                } elsif ($typ == 112) {
                    $xg .= "$elon1 $elat1\n";
                } elsif ($typ == 113) {
                    $xg .= "$elon1 $elat1\n";
                    last;
                } else {
                    $line = join(" ",@{$ra});
                    pgm_exit(1,"Type $typ: line : $line! *** FIX ME ***\n");
                }
                $cnt++;
            }
            $xg .= "$elon2 $elat2\n";
            $xg .= "NEXT\n";
        } elsif ($typ == 20) {
            # 0   1            2            3        4 5 6
            # 20  50.34385470  008.87837095 358.5000 0 2 {@Y}{^l}A
            # 20  50.34074168  008.87848957 178.5000 0 2 {@Y}{^r}C{@@}C{^l}
            # 20  50.34048600  008.87849905 358.5000 0 2 {@Y}{^l}C{@@}C{^r}
            # 20  50.33713988  008.87862637 178.5000 0 2 {@Y}{^r}D{@@}D{^l}
            # 20  50.33693452  008.87863396 358.5000 0 2 {@Y}{^l}D{@@}D{^r}
            # 20  50.33314446  008.87877832 180.0000 0 2 {@Y}{^r}A
            $elat1 = ${$ra}[1];
            $elon1 = ${$ra}[2];
            set_bounds(\@bounds,$elat1,$elon1);
            $name = join(' ', splice(@{$ra},6)); # Letters
            $name = trim_sign($name);
            $xg .= "anno $elon1 $elat1 $name\n" if (length($name));
        } elsif ($typ == 1200) {
            # readability only
        } elsif ($typ == 1201) {
            # 1201 Taxi routing node All nodes must be used in at least one edge
            # 1201  31.15839433  121.81188494 both 0 N_start
            $elat1 = ${$ra}[1];
            $elon1 = ${$ra}[2];
            set_bounds(\@bounds,$elat1,$elon1);
            $dir   = ${$ra}[3];
            $uid   = ${$ra}[4];
        } elsif ($typ == 1202) {
            # 1202 Taxi routing edge Segment in taxi routing network
            # 1202 270 255 twoway taxiway N
            $bgn  = ${$ra}[1];
            $end  = ${$ra}[2];
            if (defined $routing{$bgn} && defined $routing{$end}) {
                $rra = $routing{$bgn};
                $elat1 = ${$rra}[0];
                $elon1 = ${$rra}[1];
                $rra = $routing{$end};
                $elat2 = ${$rra}[0];
                $elon2 = ${$rra}[1];
                $xg .= "color blue\n";
                $xg .= "$elon1 $elat1\n";
                $xg .= "$elon2 $elat2\n";
                $xg .= "NEXT\n";

            } else {
                prtw("WARNING: Begin $bgn, or end $end NOT defined!\n");
            }
        } elsif ($typ == 1204) {
            # 1204 Edge active zone Identifies an edge as in a runway active zone.
            # arrival       Active zone classification “arrival” or “departure” or “ils”
            # 16L,16C       Runway(s) to which active zone refers Comma-separated list up to 4 runway identifies
            $uid  = ${$ra}[1];  # classification
            $rwy1 = ${$ra}[2];  # runways
        } elsif ($typ == 1300) {
            # 1300 37.61743710 -122.39173209 30.00 gate heavy|jets G93
            # NB: SEEMS BADLY DOCUMENTED BELOW!!!!
            # 1300 Taxi location Start or end point for taxi routes. Not linked to taxi routing network by edges (row code 1202)
            # 0 1300            Row code for taxi route start/end point 1300
            # 1 47.43931757     Latitude of location in decimal degrees Eight decimal places supported
            # 2 -122.29806851   Longitude of location object in decimal degrees Eight decimal places supported
            # 3 88.78           Heading (true) of airplane positioned at this location Decimal degrees, true heading
            # 4 gate            Type of location “gate”, “hangar”, “misc” or “tie-down”
            # 5 jets|turboprops Airplane types to that can use this location Pipe-separated list (“|”). Can include “heavy”, ”jets”,
            #                   “turboprops”, “props” and “helos” (or just “all” for all types)
            # 6 G93             Name
            $elat1 = ${$ra}[1];
            $elon1 = ${$ra}[2];
            $hdg   = ${$ra}[3];
            $uid   = ${$ra}[4]; # Type of location “gate”, “hangar”, “misc” or “tie-down”
            $rwy2  = ${$ra}[5];
            $name  = ${$ra}[6];
            $xg .= get_rectange( $elat1, $elon1, $hdg, $def_rad, $name ) if ($add_parkpos);
            if (VERB9()) {
                $line = "$typ";
                $ralen = scalar @{$ra};
                for ($j = 1; $j < $ralen; $j++) {
                    $line .= " ".${$ra}[$j];
                }
                prt("$line\n");
            }
            set_bounds(\@bounds,$elat1,$elon1);

        } elsif ($typ == 15) {
            # 15 Startup location Startup locations for airplanes at an airport Should be converted to new row code 1300
            # 0 15            Row code for a startup location 15
            # 1 47.52926674   Latitude of startup location in decimal degrees Eight decimal places supported
            # 2 -122.29919589 Longitude of startup location in decimal degrees Eight decimal places supported
            # 3 304.16        Heading (true) of an aeroplane when positioned at startup location Two decimal places recommended
            # 4 A6 Run Up     Name of startup location (list will be displayed in X-Plane for each airport) Short descriptive text string – ten characters or less
            $elat1 = ${$ra}[1];
            $elon1 = ${$ra}[2];
            $hdg   = ${$ra}[3];
            $name  = ${$ra}[4];
            # $name = join(' ', splice(@{$ra},4)); # Name
            $xg .= get_rectange( $elat1, $elon1, $hdg, $def_rad, $name ) if ($add_parkpos);
            if (VERB9()) {
                $line = "$typ";
                $ralen = scalar @{$ra};
                for ($j = 1; $j < $ralen; $j++) {
                    $line .= " ".${$ra}[$j];
                }
                prt("$line\n");
            }
            set_bounds(\@bounds,$elat1,$elon1);
        }
    }
    $xg = get_bounds(\@bounds).$xg;
    if (length($xg_out) == 0) {
        $xg_out = $temp_dir.$PATH_SEP."temp.$icao.xg";
    }
    rename_2_old_bak($xg_out);    # rename any previous
    write2file($xg,$xg_out);
    prt("Airport XG written to $xg_out\n");
}


sub find_icao($) {
    my $icao = shift;
    my ($line,$type,@arr,$len,$ver,$i,$cnt,$ra,$txt,$index);
    $ver = 0;
    $cnt = scalar @apt_lines;
    my @typ_counts = ();
    for ($i = 0; $i < $typmax; $i++) {
        push(@typ_counts,0);
    }
    my $rtc = \@typ_counts;
    for ($i = 0; $i < $cnt; $i++) {
        $line = $apt_lines[$i];
        chomp $line;
        $line = trim_all($line);
        $len = length($line);
        next if ($len == 0);
        if ($line =~ /^(\d+)\s+Version/) {
            $ver = $1;
            #1234567890123456789012345678901234567890123456789
            #1000 Version - data cycle 2013.10, build 20131335
            prt(substr($line,0,49)."\n");
            #prt("Version $ver\n");
            next;
        }
        next if ($ver == 0);
        @arr = split(/\s+/,$line);
        $type = $arr[0];
        last if ($type == 99);
        if (($type == 1)||($type == 16)||($type == 17)) {
            if ($icao eq $arr[4]) {
                $index = set_type_count($type,$rtc,$line);
                my @a = @arr;
                push(@my_apt,\@a);
                $i++;
                for (; $i < $cnt; $i++) {
                    $line = $apt_lines[$i];
                    chomp $line;
                    $line = trim_all($line);
                    $len = length($line);
                    next if ($len == 0);
                    @arr = split(/\s+/,$line);
                    $type = $arr[0];
                    last if ($type == 99);
                    last if (($type == 1)||($type == 16)||($type == 17));
                    $index = set_type_count($type,$rtc,$line);
                    my @a2 = @arr;
                    push(@my_apt,\@a2);
                }
                last;
            }
        }
    }
    if (length($out_file) == 0) {
        $out_file = $temp_dir.$PATH_SEP."temp.$icao.dat"
    }
    $cnt = scalar @my_apt;
    prt("Found $icao, $cnt items... to be written to $out_file\n");
    $txt = '';
    foreach $ra (@my_apt) {
        $line = join(" ",@{$ra});
        $txt .= "$line\n";
    }
    rename_2_old_bak($out_file);    # rename any previous
    write2file($txt,$out_file);
    prt("Extract of $icao written to $out_file\n");
    write_xg_file(\@my_apt);
    ###########################################################
    for ($i = 0; $i < $typmax; $i++) {
        $cnt = ${$rtc}[$i];
        $txt = $typ_text{$i};
        prt("$txt $cnt\n") if ($cnt);
    }
    #################################################################################
    ### 
    if (length($cmpdat)) {
        my ($cnt2);
        my @typ_count2 = ();
        for ($i = 0; $i < $typmax; $i++) {
            push(@typ_count2,0);
        }
        my $rtc2 = \@typ_count2;
        if ($cmpdat =~ /\.gz$/i) {
            if (!open(INF,"gzip -cdq $cmpdat|")) {
                pgm_exit(1,"Failed to 'open' file '$cmpdat'!\n");
            }
        } else {
            if (!open( INF, "<$cmpdat") ) {
                pgm_exit(1,"Error: Unable to open $cmpdat\n");
            }
        }
        my @cmp = <INF>;
        close INF;
        $cnt = scalar @cmp;
        prt("Got $cnt lines to compare... from $cmpdat\n");
        my @cmp_apt = ();
        $ver = 0;
        for ($i = 0; $i < $cnt; $i++) {
            $line = $cmp[$i];
            chomp $line;
            $line = trim_all($line);
            $len = length($line);
            next if ($len == 0);
            if ($line =~ /^(\d+)\s+Generated/i) {
                $ver = $1;
                #prt("Version $ver\n");
                prt("$line\n");
                next;
            } elsif ($line =~ /^(\d+)\s+Version/i) {
                $ver = $1;
                #prt("Version $ver\n");
                prt("$line\n");
                next;
            }
            next if ($ver == 0);
            @arr = split(/\s+/,$line);
            $type = $arr[0];
            last if ($type == 99);
            #$index = set_type_count($type,$rtc2,$line);
            #my @a = @arr;
            #push(@cmp_apt,\@a);
            if (($type == 1)||($type == 16)||($type == 17)) {
                if ($icao eq $arr[4]) {
                    $index = set_type_count($type,$rtc2,$line);
                    my @a = @arr;
                    push(@cmp_apt,\@a);
                    $i++;
                    for (; $i < $cnt; $i++) {
                        $line = $cmp[$i];
                        chomp $line;
                        $line = trim_all($line);
                        $len = length($line);
                        next if ($len == 0);
                        @arr = split(/\s+/,$line);
                        $type = $arr[0];
                        last if ($type == 99);
                        last if (($type == 1)||($type == 16)||($type == 17));
                        $index = set_type_count($type,$rtc2,$line);
                        my @a2 = @arr;
                        push(@cmp_apt,\@a2);
                    }
                    last;
                }
            }
        }
        $cnt = scalar @cmp_apt;
        if ($ver == 0) {
            prtw("WARNING: Failed to find 'version' line in $cmpdat file!\n");
        } elsif ($cnt == 0) {
            prtw("WARNING: Failed to find $icao in $cmpdat file!\n");
        } else {
            prt("Got $cnt $icao apt lines from $cmpdat\n");
            for ($i = 0; $i < $typmax; $i++) {
                $cnt = ${$rtc}[$i];
                $cnt2 = ${$rtc2}[$i];
                $txt = $typ_text{$i};
                prt("$txt $cnt vs $cnt2\n") if ($cnt || $cnt2);
            }
            $xg_out = $temp_dir.$PATH_SEP."temp.$icao.comp.xg";
            write_xg_file(\@cmp_apt);
            prt("Done compare...\n");
        }
    }
}

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

sub list_apts_w_taxi() {
    my ($line,$type,@arr,$len,$ver,$i,$cnt,$ra,$txt,$icao,$aptcnt,$atyp);
    $ver = 0;
    $cnt = scalar @apt_lines;
    my %apts = ();
    $aptcnt = 0;
    for ($i = 0; $i < $cnt; $i++) {
        $line = $apt_lines[$i];
        chomp $line;
        $line = trim_all($line);
        $len = length($line);
        next if ($len == 0);
        if ($line =~ /^(\d+)\s+Version/) {
            $ver = $1;
            #1234567890123456789012345678901234567890123456789
            #1000 Version - data cycle 2013.10, build 20131335
            prt(substr($line,0,49)."\n");
            #prt("Version $ver\n");
            next;
        }
        next if ($ver == 0);
        @arr = split(/\s+/,$line);
        $type = $arr[0];
        last if ($type == 99);
        if (($type == 1)||($type == 16)||($type == 17)) {
            $aptcnt++;  # land airports
            $icao = $arr[4];
            $atyp = $type;
        } elsif ($type == 20) {
            if (defined $apts{$icao}) {
                $apts{$icao}++;
            } else {
                $apts{$icao} = 1;
            }
        }
    }

    @arr = sort keys %apts;
    $cnt = scalar @arr;
    prt("Found $aptcnt airports (1,16,17), $cnt with signs...\n");
    $txt = "Found $aptcnt airports (1,16,17), $cnt with signs...\n";
    my @signs = ();
    foreach $icao (@arr) {
        $cnt = $apts{$icao};
        push(@signs,[$icao,$cnt]);
        ##prt("$icao $cnt\n");
    }
    @signs = sort mycmp_acend_n1 @signs;
    prt("List ordered by sign count...\n");
    foreach $ra (@signs) {
        $line = join("\t",@{$ra});
        $txt .= "$line\n";
        prt("$line\n");
    }
    if (length($out_file) == 0) {
        $out_file = $temp_dir.$PATH_SEP."tempsigns.txt"
    }
    rename_2_old_bak($out_file);    # rename any previous
    write2file($txt,$out_file);
    prt("List written to $out_file\n");
    #$load_log = 1;
    #pgm_exit(1,"TEMP EXIT\n");
}

#########################################
### MAIN ###
parse_args(@ARGV);
load_apt_dat();
if ($writesigns) {
    list_apts_w_taxi();
} else {
    find_icao($in_icao);
}
pgm_exit(0,"");
########################################

sub need_arg {
    my ($arg,@av) = @_;
    pgm_exit(1,"ERROR: [$arg] must have a following argument!\n") if (!@av);
}

sub parse_args {
    my (@av) = @_;
    my ($arg,$sarg);
    my $verb = VERB2();
    while (@av) {
        $arg = $av[0];
        if ($arg =~ /^-/) {
            $sarg = substr($arg,1);
            $sarg = substr($sarg,1) while ($sarg =~ /^-/);
            if (($sarg =~ /^h/i)||($sarg eq '?')) {
                give_help();
                pgm_exit(0,"Help exit(0)");
            } elsif ($sarg =~ /^v/) {
                if ($sarg =~ /^v.*(\d+)$/) {
                    $verbosity = $1;
                } else {
                    while ($sarg =~ /^v/) {
                        $verbosity++;
                        $sarg = substr($sarg,1);
                    }
                }
                $verb = VERB2();
                prt("Verbosity = $verbosity\n") if ($verb);
            } elsif ($sarg =~ /^l/) {
                if ($sarg =~ /^ll/) {
                    $load_log = 2;
                } else {
                    $load_log = 1;
                }
                prt("Set to load log at end. ($load_log)\n") if ($verb);
            } elsif ($sarg =~ /^o/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $out_file = $sarg;
                prt("Set out file to [$out_file].\n") if ($verb);
            } elsif ($sarg =~ /^x/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $xg_out = $sarg;
                prt("Set xgraph out file to [$xg_out].\n") if ($verb);
            } elsif ($sarg =~ /^p/) {
                $add_parkpos = 1;
                prt("Set to add parkpos to xgraph out file.\n") if ($verb);
            } elsif ($sarg =~ /^a/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $aptdat = $sarg;
                if (-f $aptdat) {
                    prt("Set apt.dat to [$aptdat].\n") if ($verb);
                } else {
                    pgm_exit(1,"ERROR: Unable to 'stat' new $aptdat\n");
                }
            } elsif ($sarg =~ /^c/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $cmpdat = $sarg;
                if (-f $cmpdat) {
                    prt("Set compare dat to [$cmpdat].\n") if ($verb);
                } else {
                    pgm_exit(1,"ERROR: Unable to 'stat' compare $cmpdat\n");
                }
            } elsif ($sarg =~ /^s/) {
                $writesigns = 1;
            } else {
                pgm_exit(1,"ERROR: Invalid argument [$arg]! Try -?\n");
            }
        } else {
            $in_icao = $arg;
            prt("Set input to [$in_icao]\n") if ($verb);
        }
        shift @av;
    }

    if ($debug_on) {
        prtw("WARNING: DEBUG is ON!\n");
        if ($debug_on == 1) {
            # $verbosity = 9;
            $add_parkpos = 1;
            # $load_log = 1;
            if (length($in_icao) ==  0) {
                $in_icao = $def_file;
                prt("Set DEFAULT input to [$in_icao]\n");
            }
            if ((length($cmpdat) == 0) && length($def_cmp) && (-f $def_cmp)) {
                $cmpdat = $def_cmp;
                prt("Set DEFAULT compare to [$cmpdat]\n");
            }
        } else {
            $writesigns = 1;
            prt("Set DEFAULT to write signs\n");
        }
    }
    if ((length($in_icao) ==  0) && ($writesigns == 0)) {
        pgm_exit(1,"ERROR: No ICAO nor --signs found in command!\n");
    } elsif ($writesigns && length($in_icao)) {
        pgm_exit(1,"Got BOTH an icao $in_icao, AND --signs!\nCan only do one or the other!\n");
    }

    if (! -f $aptdat) {
        pgm_exit(1,"ERROR: Unable to find in file [$aptdat]! Check name, location...\n");
    }
}

sub give_help {
    prt("$pgmname: version $VERS\n");
    prt("Usage: $pgmname [options] ICAO\n");
    prt("Options:\n");
    prt(" --help  (-h or -?) = This help, and exit 0.\n");
    prt(" --verb[n]     (-v) = Bump [or set] verbosity. (def=$verbosity)\n");
    prt(" --load        (-l) = Load LOG at end. ($outfile)\n");
    prt(" --out <file>  (-o) = Write output to this file.\n");
    prt(" --apt <file>  (-a) = Given name of apt.dat.gz to use.\n");
    prt("   Default is '$aptdat'. ".(( -f $aptdat ) ? "ok" : "NOT FOUND!")."\n");
    prt(" --comp <file> (-c) = Compare extract to this file.\n");
    prt(" --signs       (-s) = Generate a list of airports with signs.\n");
    prt(" --xg <file>   (-x) = Set xgraph output file.\n");
    prt(" --parkpos     (-p) = Add parking positions to xgraph output.\n");
}

sub apt1000_spec() {
    my $txt = <<EOF;
from : http://data.x-plane.com/file_specs/XP%20APT1000%20Spec.pdf
Code Meaning Comment
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 use ‘or’ logic, rules of a different type use
‘and’ logic). First flow to pass all rules is used.
 1001 Traffic flow wind rule Zero, one or many for a flow. Multiple rules use ‘or’ logic.
 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 Zero, one or many for a flow. Multiple rules use ‘or’ logic.

 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 (deprecates code 15) Not explicitly connected to taxi route network

 50 – 56 Communication frequencies Zero, one or many for each airport

100 Land Runway
100         Row code for a land runway (the most common) 100
29.87       Width of runway in metres Two decimal places recommended. Must be >= 1.00
1           Code defining the surface type (concrete, asphalt, etc) Integer value for a Surface Type Code (see below)
0           Code defining a runway shoulder surface type 0=no shoulder, 1=asphalt shoulder, 2=concrete shoulder
0.15        Runway smoothness (not used by X-Plane yet) 0.00 (smooth) to 1.00 (very rough). Default is 0.25
0           Runway centre-line lights 0=no centerline lights, 1=centre line lights
2           Runway edge lighting (also implies threshold lights) 0=no edge lights, 2=medium intensity edge lights
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
13L         Runway number (eg. “31R”, “02”). Leading zeros are required. Two to three characters. Valid suffixes: “L”, “R” or “C” (or blank)
47.53801700 Latitude of runway end (on runway centerline) in decimal degrees Eight decimal places supported
-122.307461 Longitude of runway end (on runway centerline) in decimal degrees Eight decimal places supported
73.15       Length of displaced threshold in metres (this is included in implied runway length)
            A displaced threshold will always be inside (between) the two runway ends
            Two decimal places (metres). Default is 0.00
0.00        Length of overrun/blast-pad in metres (not included in implied runway length) Two decimal places (metres). Default is 0.00
2           Code for runway markings (Visual, non-precision, precision) Integer value for Runway Marking Code (see below)
0           Code for approach lighting for this runway end Integer value for Approach Lighting Code (see below)
0           Flag for runway touchdown zone (TDZ) lighting 0=no TDZ lighting, 1=TDZ lighting
1           Code for Runway End Identifier Lights (REIL) 0=no REIL, 1=omni-directional REIL, 2=unidirectional REIL

101 Water runway
101           Row code for a water runway 101
49            Width of runway in metres Two decimal places recommended. Must be >= 1.00
1             Flag for perimeter buoys 0=no buoys, 1=render buoys
 The following fields are repeated for each end of the water runway
08            Runway number. Not rendered in X-Plane (it’s on water!) Valid suffixes are “L”, “R” or “C” (or blank)
35.04420911   Latitude of runway end (on runway centerline) in decimal degrees Eight decimal places supported
-106.59855711 Longitude of runway end (on runway centerline) in decimal degrees Eight decimal places supported

102 Helipad
102           Row code for a helipad 101
H1            Designator for a helipad. Must be unique at an airport. Usually “H” suffixed by an integer (eg. “H1”, “H3”)
47.53918248   Latitude of helipad centre in decimal degrees Eight decimal places supported
-122.30722302 Longitude of helipad centre in decimal degrees Eight decimal places supported
2.00          Orientation (true heading) of helipad in degrees Two decimal places recommended
10.06         Helipad length in metres Two decimal places recommended (metres), must be >=1.00
10.06         Helipad width in metres Two decimal places recommended (metres), must be >= 1.00
1             Helipad surface code Integer value for a Surface Type Code (see below)
0             Helipad markings 0 (other values not yet supported)
0             Code defining a helipad shoulder surface type 0=no shoulder, 1=asphalt shoulder, 2=concrete shoulder
0.25          Helipad smoothness (not used by X-Plane yet) 0.00 (smooth) to 1.00 (very rough). Default is 0.25
0             Helipad edge lighting 0=no edge lights, 1=yellow edge lights

110 Pavement (taxiways) Defines an arbitrary pavement shape
110           Row code for a pavement chunk header (must be followed by a set of nodes) 110
1             Code defining the surface type (concrete, asphalt, etc) Integer value for a Surface Type Code (see below)
0.25          Runway smoothness (not used by X-Plane yet) 0.00 (smooth) to 1.00 (very rough). Default is 0.25
150.29        Orientation (true degrees) of pavement texture ‘grain’ Two decimal places recommended
A2 Exit       Description of pavement chunk (not used by X-Plane) Text string

120 Linear feature Painted surface markings & light strings

130          Airport boundary Boundary for future terrain ‘flattening’
120          Row code for a linear feature or airport boundary 120 or 130
Line B1      Description of feature or boundary (not used by X-Plane) Text string

111 Node Node (plain)
112 Node Node with Bezier control point
113 Node Node (close loop), to close boundary
114 Node Node (close loop) with Bezier control point
115 Node Node (end) to terminate a line
116 Node Node (end) with Bezier control point
112 Row code for a node. First node must follow an appropriate header row 111 thru 116
 47.53752190    [All nodes] Latitude of node in decimal degrees Eight decimal places supported
 -122.30826710  [All nodes] Longitude of node in decimal degrees Eight decimal places supported
   47.53757385  [112, 114, 116 only] Latitude of Bezier control point in decimal degrees Eight decimal places supported. Ignore for 111, 113, 115
 -122.30824831  [112, 114, 116 only] Latitude of Bezier control point in decimal degrees Eight decimal places supported. Ignore for 111, 113, 115
 3              [Not for 115 or 116] Code for painted line type on line segment starting at this node Integer Line Type Code (see below). Not for 115 or 116
 102            [Not for 115 or 116] Code for lighting on line segment starting at this node Integer Line Type Code (see below). Not for 115 or 116

14 Viewpoint Maximum of one viewpoint for each airport
14            Row code for a viewpoint 14
47.52917900   Latitude of viewpoint in decimal degrees Eight decimal places supported
-122.30434900 Longitude of viewpoint in decimal degrees Eight decimal places supported
100           Height (in feet) of viewpoint above ground level Integer
0             Code deprecated. Use ‘0’ 0
ATC Tower     Name of viewpoint (not used by X-Plane) Descriptive text string (optional)

15 Startup location Startup locations for airplanes at an airport Should be converted to new row code 1300
15            Row code for a startup location 15
47.52926674   Latitude of startup location in decimal degrees Eight decimal places supported
-122.29919589 Longitude of startup location in decimal degrees Eight decimal places supported
304.16        Heading (true) of an aeroplane when positioned at startup location Two decimal places recommended
A6 Run Up     Name of startup location (list will be displayed in X-Plane for each airport) Short descriptive text string – ten characters or less

18 Light beacon Maximum of one beacon for each airport
18            Row code for an airport light beacon 18
47.52920400   Latitude of beacon in decimal degrees Eight decimal places supported
-122.30412800 Longitude of beacon in decimal degrees Eight decimal places supported
1             Code for type of light beacon. Determines colors of beacon. Integer Beacon Type Code (see below)
BCN           Name of viewpoint (not used by X-Plane) Descriptive text string (optional)

19 Windsock Multiple windsocks permitted for each airport
19            Row code for a windsock 19
47.53900921   Latitude of windsock in decimal degrees Eight decimal places supported
-122.30868700 Longitude of windsock in decimal degrees Eight decimal places supported
1             Flag for windsock lighting 0=unlit, 1=illuminated
WS            Name of viewpoint (not used by X-Plane) Descriptive text string (optional)

20 Signs Taxiway signs or runway distance-remaining signs
20            Row code for a sign 20
47.54099177   Latitude of sign in decimal degrees Eight decimal places supported
-122.31031317 Longitude of sign in decimal degrees Eight decimal places supported
235.71        Orientation of sign in true degrees (heading of someone looking at sign’s front) Two decimal places recommended
0             Reserved for future use. Ignore. 0
2             Code for sign size Integer Sign Size Code (see below)
31R-13L       Text to be rendered on sign front and/or back Text string formatted by Sign Text Definition (see below)

21 Lighting objects VASI, PAPI, wig-wags, etc.
21            Row code for a lighting object 21
47.53666659   Latitude of lighting object in decimal degrees Eight decimal places supported
-122.30585255 Longitude of lighting object in decimal degrees Eight decimal places supported
2             Code for type of lighting object Integer Lighting Object Code (see below)
150.28        Orientation of lighting object in true degrees (looking toward object) Two decimal places recommended
3.30          Visual glideslope angle in degrees Two decimal places. 0.00 if not required. Default is 3.00
13L           Associated runway number (required for VASI/PAPI, etc) One to three characters
PAPI-2L       Description of lighting object (not used by X-Plane Short text string (optional)

1000 Traffic flow Arrival and departure traffic flows
1000           Row code for an arrival/departure traffic flow 1000
Calm and south flows Traffic flow name Descriptive name (max 50 characters)

1001 Traffic flow wind rule Zero or multiple wind rules permitted per flow
1001           Row code for a traffic flow wind rule 1001
KSEA           METAR reporting station (may be a remote airport, eg KSEA for KBFI) ICAO code, up to 6 characters
000            Wind direction minimum (magnetic) 000 - 359
359            Wind direction maximum (magnetic) 000 - 359
5              Maximum wind speed. Use 999 for ‘all’ wind speeds. 0 - 999

1002 Traffic flow ceiling rule Zero or one ceiling rule permitted per flow
1002           Row code for a traffic flow ceiling rule 1002
KSEA           METAR reporting station (may be a remote airport, eg KSEA for KBFI) ICAO code, up to 6 characters
0              Minimum reported ceiling in feet AGL at reporting station Positive integer

1003 Traffic flow visibility rule Zero or one visibility rule permitted per flow
1003           Row code for a traffic flow visibility rule 1003
KSEA           METAR reporting station (may be a remote airport, eg KSEA for KBFI) ICAO code, up to 6 characters
0              Minimum reported visibility in statute miles Float (eg. “1.5”)

1004 Traffic time rule Zero or multiple time rules permitted per flow
1004           Row code for a traffic flow time rule 1004
0000           UTC time from which rule is valid 0000 - 2400
2400           UTC time at which rule ends 0000 - 2400

1100 Runway-in-use rule Multiple rules for each flow. First to ‘pass’ is used
1100           Row code for a runway-in-use rule 1100
34C            Runway end identifier Two to three characters. Valid suffixes: “L”, “R” or “C” (or blank)
11920          Arrival or departure frequency Five digit integer, rounded DOWN where necessary
               arrivals Rule type (arrivals, departures) Pipe separated list (“|”). ‘arrivals’ and/or ‘departures’
               jets|turboprops Airplane types to which rule applies Pipe-separated list (“|”). Can include “heavy”, ”jets”,
               “turboprops”, “props” and “helos”
181359         Heading range. Not used for arrivals. 000000 - 359359
341341         Initial departure heading range. Not used for arrivals. 000000 - 359359
               Arrival 34C Rule name Descriptive name (max 50 characters)

1101 VFR pattern rule Zero or one VFR pattern rule permitted per flow
1101           Row code for a VFR traffic pattern 1101
34L            Runway end identifier Two to three characters. Valid suffixes: “L”, “R” or “C” (or blank)
left           VFR traffic pattern direction “left” or “right”

1200 Taxi routing network (for readability only)

1201 Taxi routing node All nodes must be used in at least one edge
1201          Row code for taxi routing network node 1201
47.53752190   Latitude of node in decimal degrees Eight decimal places supported
-122.30826710 Longitude of node in decimal degrees Eight decimal places supported
              both Usage of node in network (begin or end a taxi path, or both) “dest”, “init”, “both” or “junc”
5416          Node identifier (user-defined) Integer. Must be unique within scope of an airport.
A_start       Node name. Not currently used. String (max 16 characters)

1202 Taxi routing edge Segment in taxi routing network
1202          Row code for taxi routing network edge 1202
5416          Node identifier for start of edge Integer. Must refer to valid node (row code ‘1201’)
5417          Node identifier for end of edge Integer. Must refer to valid node (row code ‘1201’)
twoway        Edge can be used in both directions “twoway” or “oneway”
taxiway       Node is on a regular taxiway. If on “runway” a clearance is needed from ATC “taxiway” or “runway”
A             Taxiway identifier. Used to build ATC taxi clearances (eg. “.. .taxi via A, T, Q”) String. Taxiway or runway identifier (eg. “A” or “16L/34R”)

1204 Edge active zone Identifies an edge as in a runway active zone.
1204          Row code for an edge entering a runway active zone 1204
arrival       Active zone classification “arrival” or “departure” or “ils”
16L,16C       Runway(s) to which active zone refers Comma-separated list up to 4 runway identifies

1300 Taxi location Start or end point for taxi routes. Not linked to taxi routing network by edges (row code 1202)
1300            Row code for taxi route start/end point 1300
arrival         Active zone classification for which ATC clearance may be needed to proceed “arrival” or “departure” or “ils”
47.43931757     Latitude of location in decimal degrees Eight decimal places supported
-122.29806851   Longitude of location object in decimal degrees Eight decimal places supported
88.78           Heading (true) of airplane positioned at this location Decimal degrees, true heading
gate            Type of location “gate”, “hangar”, “misc” or “tie-down”
jets|turboprops Airplane types to that can use this location Pipe-separated list (“|”). Can include “heavy”, ”jets”,
                “turboprops”, “props” and “helos” (or just “all” for all types)

50 ATC – Recorded AWOS, ASOS or ATIS
51 ATC – Unicom Unicom (US), CTAF (US), Radio (UK)
52 ATC – CLD Clearance Delivery
53 ATC – GND Ground
54 ATC – TWR Tower
55 ATC – APP Approach
56 ATC - DEP Departure
51          Row code for an ATC COM frequency 50 thru 56 (see above)
12775       Frequency in MHz x 100 (eg. use “12322” for 123.225MHz) Five digit integer, rounded DOWN where necessary
ATIS        Descriptive name (displayed on X-Plane charts) Short text string (recommend less than 10 characters)

For Version 810
Runway or taxiway at an airport. 
10          0  Identifies this as a data line for a runway or taxiway segment. 
35.044209   1  Latitude (in decimal degrees) of runway or taxiway segment center. 
-106.598557 2  Longitude (in decimal degrees) of runway or taxiway segment center.  
08x         3  Runway number (eg “25x” or “24R”).  If there is no runway suffix (eg. “L”, “R”, “C” or "S"), 
               then an “x” is used.  “xxx” identifies the entry as a taxiway.  
               Helipads at the same airport are numbered sequentially as "H1x", H2x".  
90.439      4  True (not magnetic) heading of the runway in degrees.  Must be between 0.00 and 360.00.   
13749       5  Runway or taxiway segment length in feet. 
1000.0000   6  Length of displaced threshold (1,000 feet) for runway 08 and for the reciprocal runway 26 (0 feet).  
               The length of the reciprocal runway’s displaced threshold is expressed as the fractional part of 
               this number.  Take the runway 26 displaced threshold length  (in feet) and divide it by 10,000, 
               then add it to the displaced threshold length for runway 08.  For example, for displaced threshold 
               lengths of 543 feet and 1234 feet, the code would be 543.1234.
               Note that the displaced threshold length is included in the overall runway length but that the 
               stopway length is excluded from the overall runway length.  This code should be 0.0000 for 
               taxiway segments. FYI, the displaced threshold is usually marked (in the real world) with 
               long white arrows pointing toward the threshold.  The displaced threshold is not available 
               for use by aeroplanes landing, but may be used for take-off (in practice, if you use these 
               last few feet of the runway for take-off, you are probably in serious trouble!). 
0.1000      7  Length of stopway/blastpad/over-run at the approach end of runway 08 (0 feet) and for 
               runway 26 (1,000 feet), using the same coding structure defined above.  FYI, in the real world 
               the stopway/blastpad/over-run is usually marked with large yellow chevrons, and aeroplane 
               movements are not permitted. 
150         8  Runway or taxiway segment width in feet. 
252231      9  Runway or taxiway segment lighting codes. The first three digits ("252") define the lighting 
               for the runway as seen when approached from the direction implied by the runway number (08 in our example).
               The  final  three ("231") define the lighting for the runway as seen when approached from the 
               opposite end (26 in our example). 
               In order, these codes represent:
               Runway end “A” (08):  Visual approach path (VASI / PAPI etc.) lighting.  Here, code 2 corresponds to a VASI.
               Runway end “A” (08):  Runway lighting. Here, code 5 corresponds to TDZ lighting, which also implies centre-line 
               lighting, REIL and edge lighting.
               Runway end “A” (08):  Approach lighting.  Here, code 2 corresponds to SSALS.
               Other runway end (26):  Visual approach path (VASI / PAPI etc.) lighting. Here, code 2 corresponds to a VASI.
               Other runway end (26):  Runway lighting. Here, code 3 corresponds to REIL, which also implies edge lighting.
               Other runway end (26):  Approach lighting. Here, code 1 implies  no approach lighting.
02          10 Runway or taxiway surface code for the runway or taxiway segment.  The leading zero is optional - but 
               I always use it to keep all the columns neatly lined up. 
0           11 Runway shoulder code. These are only available in file version 701 and later. Here, code 0 implies 
               that there is no runway shoulder. 
3           12 Runway markings (the white painted markings on the surface of the runway.  Here, code 3 implies precision runway 
               markings (ie. there is an associated precision approach for the runway, either an ILS or MLS). 
0.25        13 Runway smoothness. Used to cause bumps when taxying or rolling along the runway in X-Plane.  It is on a scale of 
               0.0 to 1.0, with 0.0 being very smooth, and 1.0 being very, very rough.  X-Plane determines a baseline 
               smoothness based upon the runway surface type, and then uses this factor to determine the 'quality' 
               of the runway surface.  The default value is 0.25. 
1           14 Runway has 'distance remaining' signs (0=no signs, 1=show signs).  These are the white letters on a 
               black background on little illuminated signs along a runway, indicating the number of thousands of 
               feet of usable runway that remain.  They are inappropriate at small airports or on most dirt, 
               gravel or grass runways. 
0300.0350   15 NEW for file version 810:  Visual glideslope angle for the VASI or PAPI at each end of the 
               runway (3.00 degrees for runway 08 and 3.50 degrees for runway 26).  The angle for runway 08 
               is the whole part of this number divided by 100 (so "0300" becomes 3.00 degrees) and the angle for 
               the reciprocal runway (26) is the fractional part of this number multiplied by 100 (so "0.0350" 
               becomes 3.50 degrees).  This data is required for runways, but is NOT necessary for taxiways. 

EOF
    return $txt
}

# eof - template.pl
