geturl02.pl to HTML.

index -|- end

Generated: Tue Feb 2 17:54:41 2010 from geturl02.pl 2008/01/31 87.3 KB.

#!/perl -w
# NAME: geturl02.pl
# AIM: Get the text from a URL page ...
# 22/01/2008 - geoff mclane
# references viewed - SEE END OF FILE
# Decodes built with LOTS of empirical testsing, and an account has been taken for
# quite a number of EXCEPTIONS found in decoding the 2600 TAF from the NOAA site.
use strict;
use warnings;
###use Socket;
###use LWP::Simple;
require 'logfile.pl' or die "Unable to load logfile.pl ...\n";
require 'tafacronyms.pl' or die "Unable to load tafacronyms.pl ...\n";
# log file stuff
my ($LF);
my $pgmname = $0;
if ($pgmname =~ /\w{1}:\\.*/) {
   my @tmpsp = split(/\\/,$pgmname);
   $pgmname = $tmpsp[-1];
}
my $outfile = "temp.$pgmname.txt";
open_log($outfile);
prt( "$0 ... Hello, World ...\n" );
my $addorder = 1;
my $minkey = 18;
my $taf_indent = '# ';
my $rem_indent = '#R ';
my @aptlist = ();
my $aptcnt  = 0;
my @taflist = ();
my @exceptions = ();
# This NEEDS to be adjusted to YOUR particular default location of these files.
my $FGROOT = (exists $ENV{FG_ROOT}) ? $ENV{FG_ROOT} : "C:/FGCVS/FlightGear/data";
my $APTFILE      = "$FGROOT/Airports/apt.dat.gz";   # the airports data file
my $NAVFILE      = "$FGROOT/Navaids/nav.dat.gz";   # the NAV, NDB, etc. data file
my $testurl = 'http://weather.noaa.gov/pub/data/forecasts/taf/stations/LFPO.TXT';
my $testurl2 = 'http://weather.noaa.gov/pub/data/forecasts/taf/stations/XS50.TXT';
my $testtaf = 'KSFO 231456Z 03009KT 10SM FEW009 OVC020 06/04 A2999 RMK AO2 RAB25E33 SLP156 P0000 60003 T00610039 55000';
my $testtaf2 = '2008/01/09 04:09 NZWD TAF 090303 15022G32KT 0400 -SN BLSN OVC007 650079 520005 QNH2910INS GRID32022G32KT BECMG 1214 16017KT 1600 -SN BLSN BKN008 OVC012 620089 QNH2908INS GRID33017KT BECMG 1719 16015KT 3200 -SN SCT010 BKN010 OVC025 610159 QNH2912INS GRID33015KT BECMG 2301 16020KT 1200 -SN BLSN BKN008 OVC010 620089 510005 QNH2915INS GRID33020KT';
my $testtaf3 = '2007/09/26 09:43 TAF AMD TAF AMD CYKF 260943Z 261004 23012KT P6SM SCT015 BKN025 TEMPO 1014 6SM -SHRA BR BKN008 OVC015 FM1400Z 31012KT P6SM SCT015 BKN025 TEMPO 1420 6SM -SHRA BR BKN015 RMK FCST BASED ON AUTO OBS. NXT FCST BY 14Z';
my $testtaf4 = '2008/01/25 03:02 TAF ANYN 250302Z 250606 03010KT 9999 -SHRA SCT018 SCT050 BKN140 T 30 28 26 25 Q 1005 1006 1005 1004=';
my $testtaf5 = 'TAF KPIT 091730Z 091818 15005KT 5SM HZ FEW020 WS010/31022KT FM1930 30015G25KT 3SM SHRA OVC015 TEMPO 2022 1/2SM +TSRA OVC008CB FM0100 27008KT 5SM SHRA BKN020 0VC040 PROB40 0407 1SM -RA BR FM1015 18005KT 6SM -SHRA OVC020 BECMG 1315 P6SM NSW SKC';
my $testtaf6 = 'DFFD.TXT 2008/01/28 16:00 TAF DFFD 281600Z 281818 06010KT 8000 NSC TEMPO 0610 2000 HZ= TAF DFOO 281600Z 281818 06008KT CAVOK TEMPO 0610 2500 HZ';
my $testtaf7 = 'VIPK.TXT 2008/01/09 03:00 VIAR/VIJU/VICG/VIPK 090615 15005KT 3000 HZ SCT030 SCT100 BEC 0810 27006KT 4000 HZ BEC 1315 24005KT 3000 HZ TEMPO 0615 1200 TSRA FEW030CB';
my $testtaf8 = 'AEZS.TXT 2007/12/24 10:00 TAF SAEZS 241000Z 241212 12008KT 5000 BRDZ FEW010 SCT015 BECMG 1618 14010KT 9999 NSC TEMPO 0811 0800 BCFG BKN001';
my $testtaf9 = 'AAWE.TXT 2007/09/01 04:00 TAF SAAWE 010400Z SCT030 BKNB070 BEDCMG 1518 34015KT BKN020 BKN040 TERMPO 1824 03010KT';
my $testtaf10 = 'KSFO 291756Z 13003KT 10SM -RA SCT027 OVC034 07/04 A3031 RMK AO2 RAB52 SLP262 P0001 60001 T00720044 10072 20044 53010=';
my $testtaf11 = 'EDDF 250450Z 18004KT 4800 BR SCT018 BKN032 00/00 Q1014 8829//95 NOSIG';
my $testtaf12 = 'YSSY 301644Z 301818 35008KT 9999 FEW012 FM23 04015KT 9999 FEW040 FM08 18020G30KT 9999 -SHRA SCT012 BKN020 PROB30 INTER 0212 3000 TSRA SCT015 SCT050CB INTER 1218 4000 SHRA BKN010';
my $testtaf13 = 'LEAL.TXT 2008/01/28 17:00 TAF LEAL 281700Z 290024 VRB04KT CAVOK TX18/13Z TN02/06Z';
# I think TX is maximum temp, and TN is minimum temp forecast
my $testtaf14 = 'AMDA.TXT 2008/01/16 17:00 TAF AMDA 161700Z 170024 10004KT 6000 SCT018 BECMG 0103 32010KT 7000 TEMPO 0407 5000 RA FEW017CB BKN018 BECMG 1012 10005KT 6000';
my $testtaf15 = 'DRRB.TXT 2007/06/30 12:00 301200Z METAR DRRB 301200Z 28004KT 9/9 FEW040 BKN300 35/22 QNIL';
my $testtaf16 = 'DGKT.TXT 2007/12/20 09:00 TAF DGKT 201212 ?0010KT 9999 SCP016 TEMPO F?W028CB B?CMG 1820 VRB05KT SCT009 TEMPO 0406 4000 BR FM 0900 22005KT 9999 SCT014';
my $testtaf17 = 'DITY.TXT 2008/01/28 07:30 TAF VALIDITY 280730Z 282109 --------------------------- WAMG 00000KT 9000 SCT018 BECMG 0103 24010KT TEMPO 0609 4000 RA FEW017CB';
my $testtaf18 = 'BNAA.TXT 2007/12/30 04:45 TAF DBNAA 300445Z 300606 06005KT 6000 NSW NSC BECMG1218 12006KT CAVOK';
my $testtaf19 = 'ECMG.TXT 2007/09/15 21:00 VAAH 15210Z 160024 29004KT 3000 FEW020 BECMG 040008KT 6000 FEW020 SCT025 BECVG 1315 00000KT 5000 HZ FEW020 SCT080 ) BECMG 1820 4000 HZ';
my $testtaf20 = 'DXFG.TXT 2007/10/11 15:30 DXFG 111530Z 111818 00000KP 999) FEW013 PROB30 TEMPO 1820 TS SCT013 FEW023CB TEMPO 1418 36010KT TS SCT016 ?EW0?3CB';
my $testtaf21 = 'BGTL.TXT 2008/01/28 11:03 TAF TAF BGTL 281111 09009KT 9999 FEW030 QNH2978INS TEMPO 1806 13016KT 9999 -SHSN FEW015 BKN025 510003 TM21/03Z TM26/13Z';
# debugging stuff
my $dodebug = 1;
my $dotest = 0;
my $showall = 1;   # show each entry decoded
my $verbose = 0;
my $beginatt = 0;
my $endattaf = 100;
if ($dodebug) {
   $dotest = 1;
   $showall = 1;
   ##$verbose = 1;
}
my $acttaf = '';
my @warnings = ();
#my $cannedtaf = 'taf20080125.txt';
my $cannedtaf = 'taf20080128.txt';
# apt.dat.gz CODES - see http://x-plane.org/home/robinp/Apt810.htm for DETAILS
my $aln =     '1';   # airport line
my $rln =    '10';   # runways/taxiways line
my $sealn =  '16'; # Seaplane base header data.
my $heliln = '17'; # Heliport header data.  
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
##load_airport_file($APTFILE);
$aptcnt = scalar @aptlist;
prt( "Got $aptcnt airports loaded ...\n" );
for (my $i = 1; $i <= $aptcnt; $i++) {
   show_airport($i);
}
if ($dotest) {
   #push(@taflist, $testtaf2);
   #push(@taflist, $testtaf2);
   #push(@taflist, $testtaf3);
   #push(@taflist, $testtaf4);
   #push(@taflist, $testtaf5);
   #push(@taflist, $testtaf6);
   #push(@taflist, $testtaf7);
   #push(@taflist, $testtaf8);
   #push(@taflist, $testtaf9);
   #push(@taflist, $testtaf10);
   #push(@taflist, $testtaf11);
   #push(@taflist, $testtaf12);
   #push(@taflist, $testtaf13);
   #push(@taflist, $testtaf14);
   #push(@taflist, $testtaf15);
   #push(@taflist, $testtaf16);
   #push(@taflist, $testtaf17);
   #push(@taflist, $testtaf18);
   #push(@taflist, $testtaf19);
   #push(@taflist, $testtaf20);
   push(@taflist, $testtaf21);
   prt( "Doing TEST of ".scalar @taflist." TAF records ...\n" );
} else {
   ###fetch_url( $testurl );
   if (open TF, "<$cannedtaf") {
      @taflist = <TF>;
      close TF;
      prt( "Doing ".scalar @taflist." TAF records from $cannedtaf file ...\n" );
   } else {
      prt( "WARNING: Can NOT locate file $cannedtaf ...\n" );
      ###push(@taflist, $testtaf2);
   }
}
process_taf_list();
if (@warnings) {
   prt( "\nWARNINGS FOUND ".scalar @warnings." ...\n" );
   foreach my $warn (@warnings) {
      prt( "$warn\n" );
   }
   prt("\n");
} else {
   prt( "No warnings listed ...\n\n" );
}
if (@exceptions) {
   prt( "List of ".scalar @exceptions." Exceptions ...\n" );
   prt( "my \@tafexceptions = qw(\n" );
   my $wrap = 0;
   foreach my $ex (@exceptions) {
      prt( "$ex " );
      $wrap++;
      if ($wrap > 6) {
         prt( "\n" );
         $wrap = 0;
      }
   }
   prt( ");\n" );
}
close_log($outfile,1);
exit(0);
sub process_taf_list {
   my ($ttcnt, $dcnt, @tafarr, $tafcnt, $t);
   $ttcnt = scalar @taflist;
   prt("Processing list of $ttcnt TAF entries ... (begin=$beginatt, end=$endattaf, showall=".
      ($showall ? 'Yes' : 'No').
      ", verbose=".($verbose ? "Yes" : "No").")\n");
   $dcnt = 0;
   $ttcnt = 0;
   foreach my $taf (@taflist) {
      $ttcnt++;
      if ($endattaf > 0) {
         if ($ttcnt < $beginatt) {
            next;
         }
         if ($ttcnt > $endattaf) {
            last;
         }
      }
      chomp $taf;
      if ($taf =~ /=/) {
         @tafarr = split(/=/,$taf);
         $tafcnt = scalar @tafarr;
         for ($t = 0; $t < $tafcnt; $t++) {
            $taf = trim_all($tafarr[$t]);
            if ((length($taf) > 12) && ($taf =~ /^\S+\s+\S+\s+\S+\s+/)) {
               $dcnt += show_taf( $taf, $ttcnt );
            }
         }
      } else {
         $dcnt += show_taf($taf, $ttcnt);
      }
   }
   prt( "\nShown $dcnt TAF entries ...\n" );
}
sub fetch_url_NOT_USED {   # see gettaf01.pl
   my ($url) = shift;
   prt( "Fetching: $url\n" );
   my $txt = get($url);
   if ($txt && length($txt)) {
      prt( "$txt\n" );
      my $taftxt = $txt;
      $taftxt =~ s/\n/ /gm;
      $taftxt =~ s/\r/ /gm;
      $taftxt =~ s/\t/ /gm;
      $taftxt = trim_all($taftxt);
      prt( "$taftxt\n" );
      push(@taflist,$taftxt);
   } else {
      prt( "FAILED to get URL $url ...\n" );
   }
}
sub show_remarks {
   my ($hrem, $rem) = @_;
   my $cnt = scalar keys(%{$hrem});
   my $msg = "REMARKS: $cnt [$rem]";
   prt( "$msg ...\n" );
   my ($mg);
   foreach my $key (sort keys( %{$hrem} )) {
      my $hash = $$hrem{$key};
      ##prt( " $key: $hash\n" );
      $msg = "$key ";
      $msg = substr($msg,4) if ($addorder);
      $msg = "$rem_indent$msg";
      $msg .= '.' while (length($msg) < $minkey);
      $msg .= ': ';
      if (ref($hash) eq "HASH") {
         $mg = '';
         foreach my $k (keys %{$hash}) {
            my $v = ${%{$hash}}{$k};
            $mg .= ', ' if length($mg);
            $mg .= "$k=$v";
         }
      } else {
         $mg = $hash;
      }
      $msg .= $mg;
      prt( "$msg\n" );
   }
}
sub show_taf {
   my ($taftxt, $tnum) = @_;
   my $msg = "DECODE:$tnum: $taftxt";
   my $mg = '';
   my $rmk = '';
   my %metar = decode_metar($taftxt);
   if ($showall || (defined $metar{'999.UNDECODED'}) || (defined $metar{'UNDECODED'})) {
      prt( "\n$msg\n" );
      foreach my $key (sort keys(%metar)) {
         my $hash = $metar{$key};
         #if (($key eq 'wind')||($key eq 'visibility')) { # ||($key eq 'clouds'))
         $msg = "$key ";
         $msg = substr($msg,4) if ($addorder);
         $msg = "$taf_indent$msg";
         $msg .= '.' while (length($msg) < $minkey);
         $msg .= ': ';
         if (ref($hash) eq "HASH") {
            $mg = '';
            foreach my $k (keys %{$hash}) {
               my $v = ${%{$hash}}{$k};
               $mg .= ', ' if length($mg);
               $mg .= "$k=$v";
            }
         } else {
            #$mg = $metar{$key};
            $mg = $hash;
         }
         $msg .= $mg;
         prt( "$msg\n" );
         if ($key =~ /remarks/) {
            $rmk .= ' ' if (length($rmk));
            $rmk .= $mg;
         }
      }
      if (length($rmk)) {
         my %remarks = decode_remarks($rmk);
         show_remarks(\%remarks, $rmk);
      }
      return 1;
   }
   return 0;
}
sub add_2_exceptions {
   my ($pt) = shift;
   foreach my $ex (@exceptions) {
      if ($pt eq $ex) {
         return 0;
      }
   }
   push(@exceptions,$pt);
   return 1;
}
sub number_format {
   my ($num, $len) = @_;
   my $rnum = $num;
   if ($len == 0) {
      $rnum = int($num);
   } else {
      my $val = 10;
      my $n = $len - 1;
      while ($n) {
         $val = $val * 10;
         $n--;
      }
      $rnum =  (int($num * $val)) / $val;
   }
   return $rnum;
}
sub get_descriptor {
   my ($desc) = shift;
   # '(MI|PR|BC|DR|BL|SH|TS|FZ)?' = Descriptor
   if ($desc eq 'MI') {
      return 'MI (Shallow)';
   } elsif ($desc eq 'BC') {
      return 'BC (Patches)';
   } elsif ($desc eq 'PR') {
      return 'PR (Partial)';
   } elsif ($desc eq 'TS') {
      return 'TS (Thunderstorm)';
   } elsif ($desc eq 'BL') {
      return 'BL (Blowing)';
   } elsif ($desc eq 'SH') {
      return 'SH (Showers)';
   } elsif ($desc eq 'DR') {
      return 'DR (Drifting)';
   } elsif ($desc eq 'FZ') {
      return 'FZ (Freezing)';
   }
   return "$desc (CHECKME)";
}
sub get_obscuration {
   my ($obs) = shift;
   if ($obs eq 'BR') {
      return 'BR (Mist >= 5/8SM)';
   } elsif ($obs eq 'FG') {
      return 'FG (Fog < 5/8SM)';
   } elsif ($obs eq 'FU') {
      return 'FU (Smoke)';
   } elsif ($obs eq 'VA') {
      return 'VA (Volcanic Ash)';
   } elsif ($obs eq 'SA') {
      return 'SA (Sand)';
   } elsif ($obs eq 'HZ') {
      return 'HZ (Haze)';
   } elsif ($obs eq 'PY') {
      return 'PY (Spray)';
   } elsif ($obs eq 'DU') {
      return 'DU (Widespread dust)';
   }
   ###return "$obs (CHECKME)";
   return get_precipitation($obs);
}
sub get_precipitation {
   my ($prc) = shift;
   if ($prc eq 'DZ') {
      return 'DZ (Drizzle)';
   } elsif ($prc eq 'RA') {
      return 'RA (Rain)';
   } elsif ($prc eq 'SN') {
      return 'SN (Snow)';
   } elsif ($prc eq 'SG') {
      return 'SG (Snow grains)';
   } elsif ($prc eq 'IC') {
      return 'IC (Ice crystals)';
   } elsif ($prc eq 'PL') {
      return 'PL (Ice pellets)';
   } elsif ($prc eq 'GR') {
      return 'GR (Hail)';
   } elsif ($prc eq 'GS') {
      return 'GS (Small hail/snow pellets)';
   } elsif ($prc eq 'UP') {
      return 'UP (Unknown precipitation in automated observations)';
   } elsif ($prc eq 'BRDZ') {
      return 'BRDZ (Mist/Drizzle)';
   }
   return "$prc (CHECKME)";
}
sub get_other {
   my ($oth) = shift;
   if ($oth eq 'SQ') {
      return 'SQ (Squall)';
   } elsif ($oth eq 'SS') {
      return 'SS (Sandstorm)';
   } elsif ($oth eq 'DS') {
      return 'DS (Duststorm)';
   } elsif ($oth eq 'PO') {
      return 'PO (Well developed)';
   } elsif ($oth eq 'FC') {
      return 'FC (Funnel cloud)';
   } elsif ($oth eq '+FC') {
      return '+FC (tornado/waterspout)';
   }
   ### ??? dust/sand whirls
   return "$oth (CHECKME)";
}
sub get_weather {
   my ($wth) = shift;
   my $rwth = get_obscuration($wth);
   if ($rwth =~ /CHECKME/) {
      $rwth = get_descriptor($wth);
      if ($rwth =~ /CHECKME/) {
         $rwth = get_other($wth);
      }
   }
   return $rwth;
}
sub prtw {
   my ($mg, $tf) = @_;
   if ($tf ne $acttaf) {
      $acttaf = $tf;
      push(@warnings,$tf);
      prt("\nDECODE: $tf\n");
   }
   prt($mg);
   chomp $mg;
   push(@warnings,$mg);
}
sub get_next_hr {
   my ($ref, $href) = @_;
   my $tmphr = $ref;
   my $num = 0;
   my $ic = '';
   if ($addorder) {
      my @tarr = keys %{$href};
      my $fnd = 0;
      foreach $ic (@tarr) {
         if ($ic =~ /\.$tmphr$/) {
            $fnd = 1;
            last;
         }
      }
      while ($fnd) {
         $num++;
         $tmphr = $ref.$num;
         $fnd = 0;
         foreach $ic (@tarr) {
            if ($ic =~ /\.$tmphr$/) {
               $fnd = 1;
               last;
            }
         }
      }
   } else {
      while ( defined $href->{$tmphr} ) {
         $num++;
         $tmphr = $ref.$num;
      }
   }
   return $tmphr;
}
sub no_icao_found {
   my ($href) = shift;
   if ($addorder) {
      my @tarr = keys %{$href};
      foreach my $ic (@tarr) {
         if ($ic =~ /\.icao$/) {
            return 0;
         }
      }
   } else {
      if (defined $$href{'icao'}) {
         return 0;
      }
   }
   return 1;
}
sub decode_remarks {
   my ($tt) = shift;
   # TEMPO 2022 = TEMPOrary: changes expected for < 1 hour and in 
   # total, < half of 2-digit hour beginning and 2-digit hour ending time period
   # PROB40 0407  =  PROBability and 2-digit percent (30 or 40): probable
   # condition during 2-digit hour beginning and 2-digit hour ending time period
   # BECMG 1315 = BECoMinG: change expected during 2-digit
   # hour beginning and 2-digit hour ending time period
   prt( "\nDECODE OF:$tt\n" ) if ($verbose);
   my @parts = split(/\s/, $tt);
   my $num_parts = scalar @parts;
   my %decoded_remarks = ();
   my ($part, $i2, $v1, $v2, $v3, $v4, $v5, $v6, $info, $tr, $cord, $msg, $npart, $tpart);
   my $excnum = 0;
   for (my $i = 0; $i < $num_parts; $i++) {
      $part = $parts[$i];
      $cord = sprintf("%03d", $i);
      $i2 = $i + 1;
      $msg = $part;
      ###prt( "START PART $part\n" );
      $npart = ($i2 < $num_parts) ? $parts[$i2] : '';
      ### very special EXCEPTIONS found ###
      $part = 'BECMG' if (($part eq 'B?CMG')||($part eq 'BECMG?')||($part eq 'RECMG'));
      if (($part eq 'FM') && ($npart =~ /^\d{4}$/)) {
         $part = "FM$npart";
         $i = $i2;
         $i2 = $i + 1;
      }
      # F?W028CB
      $part =~ s/F\?W/FEW/;
      $part = 'BECMG' if ($part eq 'BECVG');
      if ($part eq ')') {
         next;   # just ignore it
      }
      $part = '9999' if ($part eq '999)');
      if ($part =~ /^FUW(\d{3}.*)$/) {
         $part =~ s/^FUW/FEW/;
      }
      if ($part =~ /^\?EW/) {
         $part =~ s/^\?EW/FEW/;
         $part =~ s/\?/0/;
      }
      # GRID15006KT
      if ($part =~ /^GRID(\d{5}KT)$/) {
         $part = $1;
      }
      $part = 'TEMPO' if ($part eq 'TERMPO');
      ### end special exceptions ###
      $msg .= ":$part" if ($msg ne $part);
      $msg .= ' ';
      ######################################################################
      ### THE MAIN DECODE TUMBLE ###
      ######################################################################
      if ($part =~ /^TEMPO/) {
         $info = 'No begin, end hours!';
         if ($i2 < $num_parts) {
            $npart = $parts[$i2];   # get 2-digit hour begin, 2-digit hour end
            # or exception TEMPO 082020 1100
            if ($npart =~ /^(\d{2})(\d{2})(\d{2})?$/ ) {
               $v1 = $1;
               $v2 = $2;
               $v3 = $3;
               if ($v3 && length($v3)) {
                  $info = "Day:$v3, Begin:$v1, End:$v2";
               } else {
                  $info = "Begin:$v1, End:$v2";
               }
               $i = $i2;
               $i2 = $i + 1;
               if ($i2 < $num_parts) {
                  $npart = $parts[$i2];   # visability 
                  if ($npart =~ /^(\d{4})$/ ) {
                     $v1 = $1;
                     $info .= ", Vis. $v1 meters";
                     $i = $i2;
                     $i2 = $i + 1;
                  }
               }
            }
         }
         $tr = get_next_hr('temporary', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $decoded_remarks{$tr} = $info;
         $msg .= "$tr, $info";
      } elsif ($part =~ /^PROB(\d{1,2})/ ) {
         $v1 = $1;
         $info = "Percentage $v1";
         if ($i2 < $num_parts) {
            $part = $parts[$i2];   # get 2-digit hour begin, 2-digit hour end
            if ($part =~ /(\d{2})(\d{2})/ ) {
               $v1 = $1;
               $v2 = $2;
               $info .= " Begin:$v1, End:$v2";
               $i = $i2;
            }
         }
         $tr = get_next_hr('probability', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $decoded_remarks{$tr} = $info;
         $msg .= "$tr, $info";
      } elsif ($part =~ /^BEC(MG)?(\d{4})?$/ ) {
         $v1 = $1;
         $v2 = $2;
         $npart = '';
         ###prt( "R$i2 - DOING $part ...\n" );
         $info = "No times given.";
         if ( $v2 && ($v2 =~ /(\d{2})(\d{2})/) ) {
            # has number attached
            $v3 = $1;
            $v4 = $2;
            $info = "BeginH:$v3, EndH:$v4";
         } else {
            if ($i2 < $num_parts) {
               $npart = $parts[$i2];   # get 2-digit hour begin, 2-digit hour end
               if ($npart =~ /^(\d{2})(\d{2})$/ ) {
                  $v1 = $1;
                  $v2 = $2;
                  $info = "BeginH:$v1, EndH:$v2";
                  $i = $i2;
               }
            }
         }
         $tr = get_next_hr('becoming', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $decoded_remarks{$tr} = $info;
         $msg .= "$tr, $info";
         # BECMG 1820 4000
         if ($i == $i2) {   # if BECMG followed by BEGIN/END HOURS, then
            $i2 = $i + 1;
            if ($i2 < $num_parts) {   # mayb also followed by VISIBILITY
               $npart = $parts[$i2];   # get vis., if any
               if ($npart =~ /^(\d{4}|9\/9)$/ ) {
                  $v1 = $1;
                  $i = $i2;
                  $cord = sprintf("%03d", $i);
                  $tr = get_next_hr('visibility', \%decoded_remarks);
                  $tr = "$cord.$tr" if ($addorder);
                  $info = "$tr - Vis. $v1 meters.";
                  if ($v1 eq '0000') {
                     # Special low value
                     $v2 = '-1 (less than)';
                     $v3 = 50;
                     $v4 = 0.05;
                     $v5 = 164;
                     $v6 = 0.031;
                  } elsif (($v1 eq '9999')||($v1 eq '9/9')) {
                     # Special high value
                     $v2 = '1 (greater than)';
                     $v3 = 10000;
                     $v4 = 10;
                     $v5 = 32800;
                     $v6 = 6.2;
                  } else {
                     # Normal visibility, returned in both small and large units.
                     $v2 = '0 (measured)';
                     $v3 = number_format($v1, 1);
                     $v4 = number_format($v1 / 1000, 1);
                     $v5 = number_format($v1 * 3.28084, 1);
                     $v6 = number_format($v1 / 1609.344, 1);
                  }
                  #$decoded_remarks{$tr}{'visibility'} = "$v1 meters";
                  $decoded_remarks{$tr}{'prefix'} = $v2;
                  $decoded_remarks{$tr}{'meter'}  = $v3;
                  $decoded_remarks{$tr}{'km'}     = $v4;
                  $decoded_remarks{$tr}{'ft'}     = $v5;
                  $decoded_remarks{$tr}{'mile'}   = $v6;
                  $i2 = $i + 1;
                  $msg .= ", $tr, $info";
               }
            }
            $i2 = $i + 1;
         }
      } elsif ($part =~ /^INTER$/) {
         # INTER 0212 3000 TSRA SCT015 SCT050CB INTER 1218 4000 SHRA BKN010 
         # INTER 0153/0453 6000 
         $info = "INTER - no times!";
         if ($i2 < $num_parts) {
            $part = $parts[$i2];   # get 2-digit hour begin, 2-digit hour end
            if ($part =~ /^(\d{2})(\d{2})\/?(\d{2})?(\d{2})?$/ ) {
               $v1 = $1;
               $v2 = $2;
               $info = "BeginH:$v1, EndH:$v2";
               $i = $i2;
            } elsif ($part =~ /^(\d{2})(\d{2})\/(\d{2})(\d{2})$/ ) {
               $v1 = $1;
               $v2 = $2;
               $v3 = $3;
               $v4 = $4;
               $info = "BeginH:$v1:$v2, EndH:$v3:$v4";
               $i = $i2;   # update to next token
            }
         }
         $tr = get_next_hr('intermediate', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $decoded_remarks{$tr} = $info;
         $msg .= "$tr, $info";
         if ($i == $i2) {
            $cord = sprintf("%03d", $i);
            $i2 = $i + 1;
            if ($i2 < $num_parts) {
               $part = $parts[$i2];   # get 4-digit visibility
               if ($part =~ /^\d{4}$/) {
                  $tr = get_next_hr('visibility', \%decoded_remarks);
                  $tr = "$cord.$tr" if ($addorder);
                  $info = "$part meters.";
                  $decoded_remarks{$tr} = $info;
                  $i = $i2;
                  $i2 = $i + 1;
                  $msg .= ", $tr, $info";
               }
            }
         }
      ###} elsif ($part =~ /^NSW$/) {
      ###   # NWS TAFs exclude turbulence, icing & temperature forecasts;
      ###   # NWS METARs exclude trend fcsts 
      ###   $tr = get_next_hr('nsw', \%decoded_remarks);
      ###   $tr = "$cord.$tr" if ($addorder);
      ###   $decoded_remarks{$tr} = 'exclude trend fcst';
      ###   $msg .= "$tr, $info";
      } elsif ($part =~ /^[MP]?(([0-9]?)[ ]?([0-9])(\/?)([0-9]*))SM$/ ) {
         # Examples:
         # 1/2SM - Visibility one-half statute mile 
         # 2 1/4SM - Visibility two and one-quarter statute miles 
         # 5SM - Visibility five statute miles 
         # P6SM - Visibility more than six statute miles  
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $v4 = $4;
         $tr = get_next_hr('visibility', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = "$1 $v2 $v3 $v4 statute miles.";
         $decoded_remarks{$tr} = $info;
         $msg .= "$tr, $info";
      } elsif (($part eq 'SKC') || ($part eq 'CLR') || ($part eq 'NSC')) {
         $v1 = $part;
         $tr = get_next_hr('conditions', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = "$v1 (Clear)";
         $decoded_remarks{$tr} = $info;
         $msg .= "$tr, $info";
      } elsif ($part =~ /^T([0-9]{2})\/([0-9]{2})Z$/) {
         # T07/18Z
         $v1 = $1;
         $v2 = $2;
         $tr = get_next_hr('time', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = "T HOUR/MINUTES Z. $v1,$v2";
         $decoded_remarks{$tr}{'hour'} = $v1;
         $decoded_remarks{$tr}{'mins'} = $v2;
         if ($i2 < $num_parts) {
            $npart = $parts[$i2];
            if ($npart =~ /([0-9]{2})([0-9]{2})([0-9]{2})/) {
               $v1 = $1;
               $v2 = $2;
               $v3 = $3;
               $tr = get_next_hr('time2', \%decoded_remarks);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= " - $tr";
               $decoded_remarks{$tr}{'day'} = $v1;
               $decoded_remarks{$tr}{'begin-hour'} = $v2;
               $decoded_remarks{$tr}{'end-hour'} = $v3;
               $i = $i2;
               $i2 = $i + 1;
            }
         }
         $msg .= "$tr, $info";
      } elsif ($part =~ /^([0-9]{3})V([0-9]{3})$/) {
         $v1 = $1;
         $v2 = $2;
         $tr = get_next_hr('wind', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = "Variable wind-direction";
         $decoded_remarks{$tr}{'var_beg'} = $1;
         $decoded_remarks{$tr}{'var_end'} = $2;
         $msg .= "$tr, $info";
      } elsif ($part =~ /^(VC)?(-|\+)?(MI|PR|BC|DR|BL|SH|TS|FZ)?((DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR)+)?(BR|FG|FU|VA|DU|SA|HZ|PY)?(PO|SQ|FC|SS)?$/ ) {
         # } elseif (ereg('^(VC)?' .                /* Proximity */
         #     '(-|\+)?' .                          /* Intensity */
         #     '(MI|PR|BC|DR|BL|SH|TS|FZ)?' .       /* Descriptor */
         #     '((DZ|RA|SN|SG|IC|PL|GR|GS|UP)+)?' . /* Precipitation */
         #     '(BR|FG|FU|VA|DU|SA|HZ|PY)?' .       /* Obscuration */
         #     '(PO|SQ|FC|SS)?$',                   /* Other */
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $v4 = $4;
         $v5 = $5;
         $v6 = $6;
         $tr = get_next_hr('weather', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = "prox.";
         if ($v1 && length($v1)&& ($v1 eq 'VC')) {
            $info .= " outside (5-10km)"
         } else {
            $info .= ' at';
         }
         $info .= ' aerodrome';
         $info .= ', inten.';
         if ($v2 && length($v2)) {
            if ($v2 eq '-') {
               $info .= ' light (-)';
            } elsif ($v2 eq '+') {
               $info .= ' heavy (+)';
            } else {
               $info .= 'unknown ($v2) CHECKME';
            }
         } else {
            # no sign
            $info .= ' moderate (no sign)';
         }
         $info .= ', desc. '.get_descriptor($v3) if ($v3 && length($v3));
         $info .= ', precip. '.get_precipitation($v4) if ($v4 && length($v4));
         $info .= ', obscur. '. get_obscuration($v5) if ($v5 && length($v5));
         $info .= ', other '.get_weather($v6) if ($v6 && length($v6));
         $decoded_remarks{$tr} = $info;
         $msg .= "$tr, $info";
      } elsif ($part =~ /^(VV|FEW|SCT|BKN|OVC|0VC)([0-9]{2,3}|\/\/\/)(CB|TCU)?$/ ) {
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $tr = get_next_hr('clouds', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = 'altitude feet ';
         if (($v2 eq '000')||($v2 eq '00')) {
            # if ($regs[2] == '000')
            # '000' is a special height.
            $v4    = 100;
            $v5    = 30;
            $info .= $v4;
            #$decoded_remarks{$tr}{'prefix'} = -1; #/* Less than */
         } elsif ($v2 eq '///') {
            # '///' means height nil
            $v4    = 'nil';
            $v5    = $v4;
            $info .= $v4;
         } else {
            $v4    = $v2 * 100;
            $v5    = int($v2 * 30.48);
            $info .= $v4;
         }
         $decoded_remarks{$tr}{'feet'}  = $v4;
         $decoded_remarks{$tr}{'meter'} = $v5;
         $npart = '';
         if ($v1 && length($v1)) {
            $npart = $v1;
            $tpart = get_acro_desc($v1);
            if ($tpart ne 'NONE') {
               $npart .= " $tpart";
            }
         }
         $npart .= " $v3" if ($v3 && length($v3));
         $decoded_remarks{$tr}{'conditions'} = $npart;
         $info .= ", $npart";
         if ($i2 < $num_parts) {
            $npart = $parts[$i2];
            if ($npart =~ /([0-9]{2})([0-9]{2})([0-9]{2})/) {
               $v1 = $1;
               $v2 = $2;
               $v3 = $3;
               $i = $i2;
               $cord = sprintf("%03d", $i);
               $tr = get_next_hr('clouds2', \%decoded_remarks);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= " - $tr";
               $decoded_remarks{$tr}{'item1'} = $v1;
               $decoded_remarks{$tr}{'item2'} = $v2;
               $decoded_remarks{$tr}{'item3'} = $v3;
               $info .= ", 6-digit CHECKME $npart";
               $i2 = $i + 1;
               if ($i2 < $num_parts) {
                  $npart = $parts[$i2];
                  if ($npart =~ /([0-9]{2})([0-9]{2})([0-9]{2})/) {
                     $v1 = $1;
                     $v2 = $2;
                     $v3 = $3;
                     $i = $i2;
                     $cord = sprintf("%03d", $i);
                     $tr = get_next_hr('clouds3', \%decoded_remarks);
                     $tr = "$cord.$tr" if ($addorder);
                     $msg .= " - $tr";
                     $decoded_remarks{$tr}{'item1'} = $v1;
                     $decoded_remarks{$tr}{'item2'} = $v2;
                     $decoded_remarks{$tr}{'item3'} = $v3;
                     $info .= ", 6-digit CHECKME $npart";
                     $i2 = $i + 1;
                  }
               }
            }
         }
         $msg .= "$tr, $info";
      } elsif ($part =~ /^FM([0-9]{2})([0-9]{2})/) {
         $v1 = $1;
         $v2 = $2;
         # eg FM1930 = FroM and 2-digit hour and 2-digit minute
         # beginning time: indicates significant change.
         # Each FM starts on a new line, indented 5 spaces.
         $tr = get_next_hr('time-range', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = "Hour=$v1, Min=$v2";
         $decoded_remarks{$tr}{'begin-hour'} = $v1;
         $decoded_remarks{$tr}{'begin-mins'} = $v2;
         $msg .= "$tr, $info";
      } elsif ($part =~ /([0-9]{3}|VRB)([0-9]{2,3})G?([0-9]{2,3})?(KT|MPS|KMH)/) {
         # Wind Group = 20006KT
         # Examples:
         #   18010KT - Wind one eight zero at one zero knots 
         # 35012G20KT - Wind three five zero at one two gust two zero knots 
         # 00000KT - Wind calm 
         # VRB16G28KT - Wind variable at one six gust two eight knots  
         $v1 = $1;   # direction, or VRB = variable
         $v2 = $2;   # wind speed
         $v3 = $3;   # gusts, if any
         $v4 = $4;   # units
         $tr = get_next_hr('wind', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         ###prt( "R$i2 - DOING $part ...\n" );
         $decoded_remarks{$tr}{'deg_true'} = $v1; # $tr = 'wind'.[nn]
         $info = "deg $v1, knots=";
         if ($v2 == 0) {
            $v1 = 0;
            $v5 = 0;
            $v6 = 0;
         } else {
            if ($v4 eq 'KT') {
               $v1 = "$v2";
               # The windspeed measured in meters per second, rounded to one decimal place
               # $meterspersec = number_format($value * 0.5144, 1);
               $v5 = number_format( ($v2 * 0.5144), 1 );
               # The windspeed measured in miles per hour, rounded to one decimal place
               $v6 = number_format( ($v2 * 1.1508), 1 );
            } elsif ($v4 eq 'MPS') {
               # The windspeed measured in meters per second */
               $v5 = number_format( $v2, 1 );
               # The windspeed measured in knots, rounded to one decimal place
               $v1 = number_format($v2 / 0.5144, 1);
               # The windspeed measured in miles per hour, rounded to one decimal place
               $v6 = number_format($v2 / 0.5144 * 1.1508, 1);
            } elsif ($v4 eq 'KMH') {
               # The windspeed measured in kilometers per hour
               $v5 = number_format($v2 * 1000 / 3600, 1);   # mps
               $v1 = number_format($v2 * 1000 / 3600 / 0.5144, 1); # knots
               # The windspeed measured in miles per hour, rounded to one decimal place
               $v6 = number_format(($v2 * 1000 / 3600 / 0.5144) * 1.1508, 1);
            } else {
               $v1 = "$v2 (KTS assumed!)";
               $v5 = number_format( ($v2 * 0.5144), 1 );
               $v6 = number_format( ($v2 * 1.1508), 1 );
               $info .= "(KTS Assumed) ";
            }
         }
         $info .= "$v1";
         $decoded_remarks{$tr}{'knots'}             = $v1;
         $decoded_remarks{$tr}{'meters_per_second'} = $v5;
         $decoded_remarks{$tr}{'miles_per_hour'}    = $v6;
         ###prt( "R$i2 - DONE $part ...\n" );
         # get VISIBILITY after WIND DIRECTION/SPEED
         if ($i2 < $num_parts) {
            $npart = $parts[$i2];   # get 4-digit visibility (meters) (I GUESS!)
            if ($npart =~ /^(\d{4}|9\/9)$/ ) {
               $v1 = $1;
               $i = $i2;
               $cord = sprintf("%03d", $i);
               $tr = get_next_hr('visibility', \%decoded_remarks);
               $tr = "$cord.$tr" if ($addorder);
               $info .= " - $tr - Vis. $v1 meters.";
               if ($v1 eq '0000') {
                  # Special low value
                  $v2 = '-1 (less than)';
                  $v3 = 50;
                  $v4 = 0.05;
                  $v5 = 164;
                  $v6 = 0.031;
               } elsif (($v1 eq '9999')||($v1 eq '9/9')) {
                  # Special high value
                  $v2 = '1 (greater than)';
                  $v3 = 10000;
                  $v4 = 10;
                  $v5 = 32800;
                  $v6 = 6.2;
               } else {
                  # Normal visibility, returned in both small and large units.
                  $v2 = '0 (measured)';
                  $v3 = number_format($v1, 1);
                  $v4 = number_format($v1 / 1000, 1);
                  $v5 = number_format($v1 * 3.28084, 1);
                  $v6 = number_format($v1 / 1609.344, 1);
               }
               #$decoded_remarks{$tr}{'visibility'} = "$v1 meters";
               $decoded_remarks{$tr}{'prefix'} = $v2;
               $decoded_remarks{$tr}{'meter'}  = $v3;
               $decoded_remarks{$tr}{'km'}     = $v4;
               $decoded_remarks{$tr}{'ft'}     = $v5;
               $decoded_remarks{$tr}{'mile'}   = $v6;
               $i2 = $i + 1;
               ###prt( "R$i2 - DONE $npart ...\n" );
            }
         }
         $msg .= "$tr, $info";
      } elsif ( $part =~ /^QNH([0-9]{4})INS/ ) {
         $v1 = $1;
         $tr = get_next_hr('altimeter', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = 'inhg '.number_format($v1 / 100, 2);
         $decoded_remarks{$tr}{'inhg'} = number_format($v1 / 100, 2);   # $tr = 'altimeter'.[nn]
         $decoded_remarks{$tr}{'mmhg'} = number_format($v1 * 0.254, 2);   # 100 inch to mm
         $decoded_remarks{$tr}{'hpa'}  = number_format($v1 * 0.338639, 2);
         $decoded_remarks{$tr}{'atm'}  = number_format(($v1 * 3.3421e-4), 3);
         $msg .= "$tr, $info";
      } elsif ($part eq 'RMK') {
         $info = "$part (ReMarK)";
         $i++;
         for ( ;$i < $num_parts; $i++) {
            $part = $parts[$i];
            $info .= " $part";
         }
         $tr = get_next_hr('remarks', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $decoded_remarks{$tr} = $info;
         $msg .= "$tr, $info";
      } elsif ($part =~ /^AMD(.*)$/) {
         $v1 = $1;
         $tr = get_next_hr('qualifier', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $info = "Qualifier of report.";
         if ($v1 && length($v1)) {
            $npart = "Amendment $v1";   # $tr = 'qualifier';
         } else {
            $npart = 'Amended';
         }
         if ($i2 < $num_parts) {
            $part = $parts[$i2];   # get 2-digit hour, 2 digit minutes (I GUESS!)
            if ($part =~ /(\d{2})(\d{2})/ ) {
               $v1 = $1;
               $v2 = $2;
               $npart .= " H:$v1 M:$v2"; 
               $info .= " H:$v1 M:$v2";
               $i = $i2;
               $i2 = $i + 1;
            }
         }
         $decoded_remarks{$tr} = $npart;
         $msg .= "$tr, $info";
      } elsif ($part =~ /^(TX|TN|T)(M)?(\d{2})\/(\d{2})Z$/) {
         # TX18/13Z TN02/06Z';
         # TM21/03Z TM26/13Z
         # I think TX is maximum temp, and TN is minimum temp forecast
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $v4 = $4;
         if ($v1 eq 'TX') {
            $npart = 'max_temp_c';
         } elsif ($v1 eq 'TN') {
            $npart = 'min_temp_c';
         } else {
            $npart = 'temp_c';
         }
         if ($v3 && ($v2 eq 'M')) {
            $v3 *= -1;
         }
         $tr = get_next_hr('temperature', \%decoded_remarks);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Max/Min Temperature.";
         $msg .= " - $tr";
         $decoded_remarks{$tr}{$npart} = "$v3 at $v4 hours.";
      } else {
         if (length($part) && (substr($part,0,1) eq '(') ) {
            # begin bracket open - go until closed
            $npart = $part;
            if ( !($npart =~ /\)$/) ) {
               while ($i2 < $num_parts) {
                  $npart .= ' '.$parts[$i2];
                  if ($npart =~ /\)$/) {
                     last;
                  }
                  $i2++;
               }
            }
            if ( $npart =~ /\)$/) {
               $tr = get_next_hr('brackets', \%decoded_remarks);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= " - $npart";
               $msg .= " - $tr";
               $decoded_remarks{$tr} = $npart;   # $tr = 'brackets'.[nn]
               $i = $i2;
               $part = '';
            }
         }
         if (length($part)) {
            $npart = get_acro_desc($part);
            if ($npart ne 'NONE') {
               $tr = get_next_hr('acronym', \%decoded_remarks);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= " - $npart";
               $msg .= " - $tr";
               $decoded_remarks{$tr}{$part} = $npart;   # $tr = 'acronym'.[nn]
               #while ($i2 < $num_parts) {
               #   $part = $parts[$i2];
               #}
               $msg .= " - acronym for $npart - $tr";
               $part = '';
            }
         }
         if (length($part) && ( is_in_taf_exceptions($part) || is_in_taf_spl_exceptions($part) || (length($part) == 1) ) ) {
            $excnum++;
            $tr = 'exception';
            $tr = "998.$tr" if ($addorder);
            $decoded_remarks{$tr}{$excnum} = $part;
            $msg .= " - $tr:$excnum";
            $part = '';
         }
         if (length($part)) {
            $tr = get_next_hr('UNDECODED', \%decoded_remarks);
            $tr = "$cord.$tr" if ($addorder);
            $decoded_remarks{$tr} = $part;
            $msg = "WARNING:ERMK: [$part] NO CASE FOR THIS!";
            prtw("$msg\n", $tt );
         }
      }
      prt( "R$i2 = $msg\n" ) if ($verbose);
   }
   return %decoded_remarks;
}
sub all_hyphens {
   my ($tx) = shift;
   if ($tx =~ /[^-]/) {
      return 0;
   }
   return 1;
}
sub decode_metar {
   my ($tt) = shift;
   my ($part, $had_dc, $msg, $cord, $had_z);
   my ($i,$i2,$v1,$v2,$v3,$v4,$v5,$v6,$tr,$icao,$npart,$tpart);
   my @parts = split(/\s/, $tt);
   my $num_parts = scalar @parts;
   my %decoded_metar = ();
   my $excnum = 0;
   $part = '';
   $had_dc = 0;
   $msg = '';
   $had_z = 0;
   $i = 0;
   $part = $parts[$i];
   $icao = '';
   if ($part =~ /^(\w{4})\.TXT$/) {
      $icao = $1;
      $cord = sprintf("%03d", $i);
      $tr = 'file';
      $tr = "$cord.$tr" if ($addorder);
      $msg = "$part - This is the FILE from the noaa site. ($icao)";
      $msg .= " - $tr";
       $decoded_metar{$tr} = "$part - ICAO=$icao";
      prt( "$msg\n" ) if ($verbose);
      $i++;
   }
   for ( ; $i < $num_parts; $i++) {
      $part = $parts[$i];
      $cord = sprintf("%03d", $i);
      $had_dc = 0;   # no DECODE yet
      $i2 = $i + 1;
      $npart = ($i2 < $num_parts) ? $parts[$i2] : '';
      $msg = $part;
      ### SOME EXCEPTIONS SEEN ###
      $part = 'BECMG' if (($part eq 'BEDCMG')||($part eq 'BUCMG')||($part eq 'BEC'));
      $part = 'TEMPO' if ($part eq 'TERMPO');
      if ($part =~ /^BKNB([0-9]{2,3})$/ ) {
         $v1 = $1;
         $part = "BKN$v1";
      }
      $part = 'BOOL' if ($part eq 'TAF?BOOL');
      if (all_hyphens($part)) {
         $msg .= ' DISCARDED';
         prt( "$msg\n" ) if ($verbose);
         $had_z = 0;   # clear any ZULU time, after this discard
         next; # and LOOP
      }
      # GRID15006KT
      if ($part =~ /^GRID(\d{5}(G\d{2})?KT)$/) {
         $part = $1;
      }
      if (($part eq 'PROB')&&($npart =~ /^\d{2}$/)) {
         $part .= $npart;
         $i = $i2;
         $i2 = $i + 1;
      }
      if (($part eq 'FM')&&($npart =~ /^\d{4}$/)) {
         $part .= $npart;
         $i = $i2;
         $i2 = $i + 1;
      }
      # HVH677
      if ($part =~ /^HVH(\d{3})/) {
         $part = substr($part,2);
      }
      ### END EXCEPTIONS SEEN ###
      $msg .= ":$part" if ($part ne $msg);
      ### THE DECODE TUMBLE ###
      # if (ereg('RMK|AFT|TEMPO|BECMG|INTER', $part))
      #if ($part =~ /(RMK|AFT|TEMPO|BECMG|INTER)/) {
      if (($part =~ /^(RMK|AFT|TEMPO|TEMP|BECMG|INTER)(\d{4})?$/)||($part =~ /^BEC([0-9]{2,4})/) ) {
         $v1 = $1;
         $tr = 'remarks';
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - The rest of the METAR is either a remark or temporary information.";
         $msg .= " - $tr";
          $decoded_metar{$tr} = $part;
         $i++;
         for ( ; $i < $num_parts; $i++) {
            $part = $parts[$i];
             $decoded_metar{$tr} .= ' '.$part;
         }
          $decoded_metar{$tr} = trim_all($decoded_metar{$tr});
         $had_dc = 1;
         last;
      } elsif (($part eq 'METAR')||($part eq 'SPECI')||($part eq 'TAF')) {
         $tr = get_next_hr('type', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Type of Report: METAR, SPECI, TAF";
         $msg .= " - $tr";
         $decoded_metar{$tr} = $part;
         $had_dc = 1;
         $had_z = 0;   # clear any ZULU time, after TYPE
      } elsif ($part =~ /^AMD$/) {
         $v1 = $1;
         $tr = get_next_hr('qualifier', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Qualifier of report.";
         $msg .= " - $tr";
         if ($v1 && length($v1)) {
            $decoded_metar{$tr} = "Amendment $v1";   # $tr = 'qualifier';
         } else {
            $decoded_metar{$tr} = 'Amended';
         }
         $had_dc = 1;
      } elsif ($part =~ /^CAVOK(=)?/) {
         $v1 = $1;
         $msg .= " - indicates Ceiling And Visibility OKay";
         # (no cloud below 5000 feet, a visibility of 6 Statute Miles or more and
         # no precipitation, thunderstorms, shallow fog, or low drifting snow) 
         # = indicates the end of the METAR report 
         if ($v1 && length($v1)) {
            $tr = 'end-report';
            $tr = "$cord.$tr" if ($addorder);
            $decoded_metar{$tr} = $part.' (Clear, no cloud 5000 feet, vis 6 miles+, no rain ...)';
            last;
         } else {
            $tr = get_next_hr('indication', \%decoded_metar);
            $tr = "$cord.$tr" if ($addorder);
            $decoded_metar{$tr} = $part.' (Clear, no cloud 5000 feet, vis 6 miles+, no rain ...)';
         }
         $msg .= " - $tr";
         $had_dc = 1;
      } elsif ( !$had_z && (($part =~ /^[A-Z]{4}$/)||($part =~ /^[A-Z]{1}\w{3}$/)) ) {
         $v1 = $part;
         $tr = get_next_hr('icao', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Station Identifier.";
         $decoded_metar{$tr} = $v1;   # $tr = 'icao'.[nn]
         $msg .= " - $tr";
         $had_dc = 1;
      } elsif ($part =~ /^AMD(.*)$/) {
         $v1 = $1;
         $tr = get_next_hr('qualifier', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Qualifier of report.";
         $msg .= " - $tr";
         if ($v1 && length($v1)) {
            $decoded_metar{$tr} = "Amendment $v1";   # $tr = 'qualifier';
         } else {
            $decoded_metar{$tr} = 'Amended';
         }
         $had_dc = 1;
      } elsif ($part =~ /^([0-9]{2})([0-9]{2})([0-9]{1,2})Z$/) {
         # ZULU TIME - plus TIME following ...
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $tr = get_next_hr('time', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - DATE, HOUR, MINUTES (ZULU).";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'day'} = $v1;
         $decoded_metar{$tr}{'hour'} = $v2;
         $decoded_metar{$tr}{'mins'} = $v3;
         # this is followed by, like
         # 260943Z 261004, 250302Z 250606, 281600Z 281818, 091730Z 091818
         # but in one case - 121100Z 12221 - ASSUME 122201
         # OR EVEN 181400Z 1815
         if ($i2 < $num_parts) {
            $npart = $parts[$i2];
            if ($npart =~ /^([0-9]{2})([0-9]{2})([0-9]{1,2})?$/) {
               $v1 = $1;
               $v2 = $2;
               $v3 = $3;
               #$tr = get_next_hr('time2', \%decoded_metar);
               #$tr = "$cord.$tr" if ($addorder);
               #$msg .= " - $tr";
               $decoded_metar{$tr}{'day2'} = $v1;
               $decoded_metar{$tr}{'begin-hour'} = $v2;
               $decoded_metar{$tr}{'end-hour'} = $v3 if ($v3 && length($v3));
               $i = $i2;
               $i2 = $i + 1;
               $msg .= ", plus $npart - BEGIN, END $v1, $v2, $v3";
            }
         }
         $had_z = 1;
         $had_dc = 1;
      } elsif ($part =~ /^T([0-9]{2})\/([0-9]{2})Z$/) {
         # T07/18Z
         $v1 = $1;
         $v2 = $2;
         $tr = get_next_hr('time', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - T HOUR/MINUTES Z.";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'hour'} = $v1;
         $decoded_metar{$tr}{'mins'} = $v2;
         if ($i2 < $num_parts) {
            $npart = $parts[$i2];
            if ($npart =~ /([0-9]{2})([0-9]{2})([0-9]{2})/) {
               $v1 = $1;
               $v2 = $2;
               $v3 = $3;
               $tr = get_next_hr('time2', \%decoded_metar);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= " - $tr";
               $decoded_metar{$tr}{'day'} = $v1;
               $decoded_metar{$tr}{'begin-hour'} = $v2;
               $decoded_metar{$tr}{'end-hour'} = $v3;
               $i = $i2;
               $i2 = $i + 1;
               $msg .= ", plus $npart - BEGIN, END $v1, $v2, $v3";
            }
         }
         $had_z = 1;
         $had_dc = 1;
      } elsif ($part =~ /([0-9]{4})\/([0-9]{2})\/([0-9]{2})/) {
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $tr = get_next_hr('record_date', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - got a DATE YYYY/MM/DD";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'year'} = $v1;
         $decoded_metar{$tr}{'month'} = $v2;
         $decoded_metar{$tr}{'day'} = $v3;
         if ($i2 < $num_parts) {
            $npart = $parts[$i2];
            if ($npart =~ /([0-9]{2}):([0-9]{2})/) {
               $v1 = $1;
               $v2 = $2;
               $decoded_metar{$tr}{'hour'} = $v1;
               $decoded_metar{$tr}{'minutes'} = $v2;
               $i  = $i2;
               $msg .= ", plus $npart - HOUR, MINUTES $v1, $v2";
            }
         }
         $had_dc = 1;
      } elsif ($part =~ /(AUTO|COR|RTD|CC[A-Z]|RR[A-Z])/ ) {
         $v1 = $1;
         $tr = get_next_hr('report_mod', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Report Modifier: AUTO, COR, CCx or RRx";
         $msg .= " - $tr";
         $decoded_metar{$tr} = $v1;
         $had_dc = 1;
      ###} elsif ($part =~ /([0-9]{3}|VRB|\?00)([0-9]{2,3})G?([0-9]{2,3})?(KT|MPS|KMH)/) {
      ###} elsif ($part =~ /^([0-9]{3}|VRB|\?00)([0-9]{2,3})G?([0-9]{2,3})?(KT|MPS|KMH|KP)$/) {
      } elsif ($part =~ /^([0-9]{3}|VRB|\?00)([0-9]{1,3})G?([0-9]{2,3})?(KT|MPS|KMH|KP)$/) {
         # Wind Group = 20006KT
         # Examples:
         #   18010KT - Wind one eight zero at one zero knots 
         # 35012G20KT - Wind three five zero at one two gust two zero knots 
         # 00000KT - Wind calm 
         # VRB16G28KT - Wind variable at one six gust two eight knots  
         $v1 = $1;   # direction, or VRB = variable
         $v2 = $2;   # wind speed
         $v3 = $3;   # gusts, if any
         $v4 = $4;   # units
         $v4 = 'KT' if ($v4 eq 'KP'); # EXCEPTION SEEN
         $tr = get_next_hr('wind', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Wind Group ";
         $msg .= "(v1-4:";
         $msg .= ($v1 && length($v1)) ? $v1 : '-';
         $msg .= ',';
         $msg .= ($v2 && length($v2)) ? $v2 : '-';
         $msg .= ',';
         $msg .= ($v3 && length($v3)) ? $v3 : '-';
         $msg .= ',';
         $msg .= ($v4 && length($v4)) ? $v4 : '-';
         $msg .= ')';
         $msg .= " - $tr";
         $decoded_metar{$tr}{'degs_true'} = $v1; # $tr = 'wind'.[nn]
         if ($v2 == 0) {
            $decoded_metar{$tr}{'knots'} = 0;
            $decoded_metar{$tr}{'meters_per_second'} = 0;
            $decoded_metar{$tr}{'miles_per_hour'} = 0;
         } else {
            if ($v4 eq 'KT') {
               $decoded_metar{$tr}{'knots'} = $v2;
               # The windspeed measured in meters per second, rounded to one decimal place
               # $meterspersec = number_format($value * 0.5144, 1);
               $decoded_metar{$tr}{'meters_per_second'} = number_format( ($v2 * 0.5144), 1 );
               # The windspeed measured in miles per hour, rounded to one decimal place
               $decoded_metar{$tr}{'miles_per_hour'} = number_format( ($v2 * 1.1508), 1 );
            } elsif ($v4 eq 'MPS') {
               # The windspeed measured in meters per second */
               $decoded_metar{$tr}{'meters_per_second'} = number_format( $v2, 1 );
               # The windspeed measured in knots, rounded to one decimal place
               $decoded_metar{$tr}{'knots'} = number_format($v2 / 0.5144, 1);
               # The windspeed measured in miles per hour, rounded to one decimal place
               $decoded_metar{$tr}{'miles_per_hour'} = number_format($v2 / 0.5144 * 1.1508, 1);
            } elsif ($v4 eq 'KMH') {
               # The windspeed measured in kilometers per hour
               $decoded_metar{$tr}{'meters_per_second'} = number_format($v2 * 1000 / 3600, 1);
               $decoded_metar{$tr}{'knots'} = number_format($v2 * 1000 / 3600 / 0.5144, 1);
               # The windspeed measured in miles per hour, rounded to one decimal place
               $decoded_metar{$tr}{'miles_per_hour'} = number_format($decoded_metar{'wind'}{'knots'} * 1.1508, 1);
            } else {
               $decoded_metar{$tr}{'knots'} = "$v2 (KTS assumed!)";
            }
         }
         $decoded_metar{$tr}{'gusts'} = "$v3 $v4" if ($v3 && length($v3));
         # Visability usually follows this wind speed and direction
         # get VISIBILITY after WIND DIRECTION/SPEED
         if ($i2 < $num_parts) {
            $part = $parts[$i2];   # get 4-digit visibility (meters) (I GUESS!)
            ###if ($part =~ /^(\d{4}|9\/9)$/ ) {
            if ($part =~ /^(\d{4}|9\/9|999\))$/ ) {
               $v1 = $1;
               $tr = get_next_hr('visibility', \%decoded_metar);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= ", - $tr - Vis. $v1 meters.";
               $v1 = '9999' if ($v1 eq '999)'); # EXCEPTION SEEN
               if ($v1 eq '0000') {
                  # Special low value
                  $v2 = '-1 (less than)';
                  $v3 = 50;
                  $v4 = 0.05;
                  $v5 = 164;
                  $v6 = 0.031;
               } elsif (($v1 eq '9999')||($v1 eq '9/9')) {
                  # Special high value
                  $v2 = '1 (greater than)';
                  $v3 = 10000;
                  $v4 = 10;
                  $v5 = 32800;
                  $v6 = 6.2;
               } else {
                  # Normal visibility, returned in both small and large units.
                  $v2 = '0 (measured)';
                  $v3 = number_format($v1, 1);
                  $v4 = number_format($v1 / 1000, 1);
                  $v5 = number_format($v1 * 3.28084, 1);
                  $v6 = number_format($v1 / 1609.344, 1);
               }
               $decoded_metar{$tr}{'prefix'} = $v2;
               $decoded_metar{$tr}{'meter'}  = $v3;
               $decoded_metar{$tr}{'km'}     = $v4;
               $decoded_metar{$tr}{'ft'}     = $v5;
               $decoded_metar{$tr}{'mile'}   = $v6;
               $i = $i2;
               $i2 = $i + 1;
            }
         }
         $had_dc = 1;
      } elsif ($part =~ /^([0-9]{3})V([0-9]{3})$/) { # !empty($decoded_metar['wind'])) {
         $v1 = $1;
         $v2 = $2;
         $tr = get_next_hr('wind', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Variable wind-direction";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'var_beg'} = $1;
         $decoded_metar{$tr}{'var_end'} = $2;
         $had_dc = 1;
      } elsif ($part =~ /^([0-9]{4}|9\/9)([NS]?[EW]?)$/) {
         # or exception 04007KT 5000HZ
         $v1 = $1;
         $tpart = $2;
         $tr = get_next_hr('visibility', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Visibility in meters (4 digits).";
         $msg .= " - $tr";
         if ($v1 eq '0000') {
            # Special low value
            $v2 = '-1 (less than)';
            $v3 = 50;
            $v4 = 0.05;
            $v5 = 164;
            $v6 = 0.031;
         } elsif (($v1 eq '9999')||($v1 eq '9/9')) {
            # Special high value
            $v2 = '1 (greater than)';
            $v3 = 10000;
            $v4 = 10;
            $v5 = 32800;
            $v6 = 6.2;
         } else {
            # Normal visibility, returned in both small and large units.
            $v2 = '0 (measured)';
            $v3 = number_format($v1, 1);
            $v4 = number_format($v1 / 1000, 1);
            $v5 = number_format($v1 * 3.28084, 1);
            $v6 = number_format($v1 / 1609.344, 1);
         }
         #$decoded_remarks{$tr}{'visibility'} = "$v1 meters";
         $decoded_metar{$tr}{'prefix'} = $v2;
         $decoded_metar{$tr}{'meter'}  = $v3;
         $decoded_metar{$tr}{'km'}     = $v4;
         $decoded_metar{$tr}{'ft'}     = $v5;
         $decoded_metar{$tr}{'mile'}   = $v6;
         $had_dc = 1;
      } elsif ($part =~ /^(VC)?(-|\+)?(MI|PR|BC|DR|BL|SH|TS|FZ)?((DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR)+)?(BR|FG|FU|VA|DU|SA|HZ|PY)?(PO|SQ|FC|SS)?$/ ) {
         # } elseif (ereg('^(VC)?' .                /* Proximity */
         #     '(-|\+)?' .                          /* Intensity */
         #     '(MI|PR|BC|DR|BL|SH|TS|FZ)?' .       /* Descriptor */
         #     '((DZ|RA|SN|SG|IC|PL|GR|GS|UP)+)?' . /* Precipitation */
         #     '(BR|FG|FU|VA|DU|SA|HZ|PY)?' .       /* Obscuration */
         #     '(PO|SQ|FC|SS)?$',                   /* Other */
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $v4 = $4;
         $v5 = $5;
         $v6 = $6;
         $tr = get_next_hr('weather', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Weather (v1-6:";
         $msg .= ($v1 && length($v1)) ? $v1 : '-';
         $msg .= ',';
         $msg .= ($v2 && length($v2)) ? $v2 : '-';
         $msg .= ',';
         $msg .= ($v3 && length($v3)) ? $v3 : '-';
         $msg .= ',';
         $msg .= ($v4 && length($v4)) ? $v4 : '-';
         $msg .= ',';
         $msg .= ($v5 && length($v5)) ? $v5 : '-';
         $msg .= ',';
         $msg .= ($v6 && length($v6)) ? $v6 : '-';
         $msg .= ')';
         $msg .= " - $tr";
         # $decoded_metar{'weather'}{'proximity'}     = $v1 if ($v1 && length($v1));
         if ($v1 && length($v1)) {
            if ($v1 eq 'VC') {
               $npart = 'outside aero(5-10km)'; # 'weather'.[nn]
            } else {
               $npart = 'outside aero(5-10km) $v1 CHECKME';
            }
         } else {
            $npart     = 'at aero';
         }
         $decoded_metar{$tr}{'prox.'} = $npart;
         ###$decoded_metar{'weather'}{'intensity'}     = $v2 if ($v2 && length($v2));
         if ($v2 && length($v2)) {
            if ($v2 eq '-') {
               $npart = 'light (-)';
            } elsif ($v2 eq '+') {
               $npart = 'heavy (+)';
            } else {
               $npart = 'unknown ($v2) CHECKME';
            }
         } else {
            # no sign
            $npart = 'mod. (no sign)';
         }
         $decoded_metar{$tr}{'inten.'}  = $npart;
         $decoded_metar{$tr}{'desc.'}   = get_descriptor($v3) if ($v3 && length($v3));
         $decoded_metar{$tr}{'precip.'} = get_precipitation($v4) if ($v4 && length($v4));
         $decoded_metar{$tr}{'obscur.'} = get_obscuration($v5) if ($v5 && length($v5));
         ###$decoded_metar{$tr}{'other'} = get_other($v6) if ($v6 && length($v6));
         $decoded_metar{$tr}{'other'}   = get_weather($v6) if ($v6 && length($v6));
         $had_dc = 1;
         # ================================================================================
      } elsif (($part eq 'SKC') || ($part eq 'CLR') || ($part eq 'NSC')) {
         $v1 = $part;
         $tr = get_next_hr('conditions', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Cloud-group.";
         $msg .= " - $tr";
         #$decoded_metar{'clouds'}{'condition'} = $part;
         $decoded_metar{$tr} = $v1.' (Clear)';
         $had_dc = 1;
      ###} elsif ($part =~ /^(VV|FEW|SCT|BKN|OVC)([0-9]{3}|\/\/\/)(CB|TCU)?$/ ) {
      } elsif ($part =~ /^(VV|FEW|SCT|BKN|OVC|SCP)([0-9]{2,3}|\/\/\/)(CB|TCU)?$/ ) {
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $v1 = 'SCT' if ($v1 eq 'SCP');   # special exception case seen
         $tr = get_next_hr('clouds', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - We have found (another) a cloud-layer-group.";
         $msg .= " - $tr";
         if ($v2 eq '000') {
            # if ($regs[2] == '000')
            # '000' is a special height.
            $decoded_metar{$tr}{'ft'}     = 100; # $tr = 'clouds'.[nn]
            $decoded_metar{$tr}{'meter'}  = 30;
            $decoded_metar{$tr}{'prefix'} = 'Less than (-1)';
         } elsif ($v2 eq '///') {
            # '///' means height nil
            $decoded_metar{$tr}{'ft'}     = 'nil';
            $decoded_metar{$tr}{'meter'}  = 'nil';
         } else {
            $decoded_metar{$tr}{'ft'}     = $v2 * 100;
            $decoded_metar{$tr}{'meter'}  = int($v2 * 30.48);
         }
         ###$decoded_metar{$tr}{'cond'}   = $v3 if ($v3 && length($v3));
         $npart = '';
         if ($v1 && length($v1)) {
            $npart = $v1;
            $tpart = get_acro_desc($v1);
            if ($tpart ne 'NONE') {
               $npart .= " $tpart";
            }
         }
         $npart .= " $v3" if ($v3 && length($v3));
         $decoded_metar{$tr}{'conditions'} = $npart;
         $msg .= ", $npart";
         $had_dc = 1;
         ################################################################
      } elsif ($part =~ /^(M?[0-9]{2})\/(M?[0-9]{2}|\/\/)?$/) {
         $v1 = $1;
         $v2 = $2;
         $tr = get_next_hr('temperature', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Temperature/Dew Point.";
         $msg .= " - $tr";
         # eg 12/08-Temperature and Dewpoint
         # 12 represents the temperature in Celcius 
         # 08 represents the dewpoint in Celcius 
         # If the temperature or dewpoint falls below 0 
         # there will be an "M" before it (i.e. 03/M02). "M" means minus. 
         $v6 = 0;
         $v3 = '';
         if ($v1 && length($v1) && ($v1 ne '//')) {
            $v6++;
            if (substr($v1,0,1) eq 'M') {
               $v1 = substr($v1,1);
               $v4 = -(($v1 * (9/5)) + 32);
               $v3 = '-';
            } else {
               $v4 = ($v1 * (9/5)) + 32;
            }
         }
         $decoded_metar{$tr}{'temp_c'} = $v3.int($v1);
         $decoded_metar{$tr}{'temp_f'} = $v3.int( ($v1 * (9/5)) + 32.5 );
         # The dewpoint could be missing, this is indicated by the
         # second group being empty at most places, but in the UK they
         # use '//' instead of the missing temperature... */
         $v3 = '';
         if ($v2 && length($v2) && ($v2 ne '//')) {
            $v6++;
            if (substr($v2,0,1) eq 'M') {
               $v2 = substr($v2,1);
               $v5 = -(($v2 * (9/5)) + 32);
               $v3 = '-';
            } else {
               $v5 = ($v2 * (9/5)) + 32;
            }
         }
         if (length($v2) && ($v2 ne '//')) {
            $decoded_metar{$tr}{'dew_c'} = $v3.int($v2);
            $decoded_metar{$tr}{'dew_f'} = $v3.int(($v2 * (9/5)) + 32.5);
            if ($v6 == 2) {
               $v6 = int(get_relative_humidity_f( $v4, $v5 ) + 0.5);
               $decoded_metar{$tr}{'relative_humidity'} = "$v6\% appx.";
            }
         } 
         $had_dc = 1;
      } elsif ($part =~ /^T([0-9]{4})([0-9]{4})/ ) {
         #         * Temperature/Dew Point Group, coded to tenth of degree Celsius.
         #   $this->store_temp($regs[1] / 10,
         #           $decoded_metar['temperature']['temp_c'],
         #           $decoded_metar['temperature']['temp_f']);
         #   $this->store_temp($regs[2] / 10,
         #           $decoded_metar['temperature']['dew_c'],
         #           $decoded_metar['temperature']['dew_f']);
         $v1 = $1;
         $v2 = $2;
         $tr = get_next_hr('temperature', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Temperature/Dew Point.";
         $msg .= " - $tr";
         $v6 = 0;
         $v3 = '';
         if ($v1 && length($v1)) {
            $v6++;
            if (substr($v1,0,1) eq 'M') {
               $v1 = substr($v1,1);
               $v3 = '-';
               $v4 = -((($v1 / 10) * (9/5)) + 32);
            } else {
               $v4 = ((($v1 / 10) * (9/5)) + 32);
            }
            $v1 /= 10;
         }
         $decoded_metar{$tr}{'temp_c'} = $v3.int($v1);
         $decoded_metar{$tr}{'temp_f'} = $v3.int( ($v1 * (9/5)) + 32.5 );
         # The dewpoint could be missing, this is indicated by the
         # second group being empty at most places, but in the UK they
         # use '//' instead of the missing temperature... */
         $v3 = '';
         if ($v2 && length($v2)&& ($v2 ne '//')) {
            $v6++;
            if (substr($v2,0,1) eq 'M') {
               $v2 = substr($v2,1);
               $v3 = '-';
               $v5 = -((($v2 / 10) * (9/5)) + 32);
            } else {
               $v5 = ((($v2 / 10) * (9/5)) + 32);
            }
            $v2 /= 10;
         }
         if (length($v2) && ($v2 ne '//')) {
            $decoded_metar{$tr}{'dew_c'} = $v3.int($v2)." (CHECKME)";
            $decoded_metar{$tr}{'dew_f'} = $v3.int(($v2 * (9/5)) + 32.5);
            if ($v6 == 2) {
               $v6 = int(get_relative_humidity_f( $v4, $v5 ) + 0.5);
               $decoded_metar{$tr}{'relative_humidity'} = "$v6\% appx.";
            }
         }
         $had_dc = 1;
      } elsif ($part =~ /^(TX|TN|T)(M)?(\d{2})\/(\d{2})Z$/) {
         # TX18/13Z TN02/06Z';
         # I think TX is maximum temp, and TN is minimum temp forecast
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $v4 = $4;
         if ($v1 eq 'TX') {
            $npart = 'max_temp_c';
         } elsif ($v1 eq 'TN') {
            $npart = 'min_temp_c';
         } else {
            $npart = 'temp_c';
         }
         if ($v3 && ($v2 eq 'M')) {
            $v3 *= -1;
         }
         $tr = get_next_hr('temperature', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Max/Min Temperature.";
         $msg .= " - $tr";
         $decoded_metar{$tr}{$npart} = "$v3 at $v4 hours.";
         $had_dc = 1;
      } elsif ($part =~ /^A([0-9]{4})/) {
         # The United States reports the altimeter setting in inches of mercury (e.g., A2992) 
         # The pressure measured in inHg.
         $v1 = $1;
         $tr = get_next_hr('altimeter', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Altimeter (A).";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'inhg'} = number_format($v1/100, 2); # $tr = 'altimeter'.[nn]
         # The pressure measured in mmHg, hPa and atm */
         $decoded_metar{$tr}{'mmhg'} = number_format($v1 * 0.254, 1);
         $decoded_metar{$tr}{'hpa'}  = int($v1 * 0.33864);
         $decoded_metar{$tr}{'atm'}  = number_format(($v1 * 3.3421e-4), 3);
         $msg .= " ($tr)";
         $had_dc = 1;
      } elsif ( $part =~ /^Q([0-9]{4}|NIL|\/{4})$/ ) {
         # internationally it will be reported in hectoPascals (milibars) (e.g., Q1016). 
         # EXCEPTION SEEN 'Q////'
         $v1 = $1;
         $tr = get_next_hr('altimeter', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Altimeter (Q).";
         $msg .= " - $tr";
         # The specification doesn't say anything about
         # the Qxxxx-form, but it's in the METARs.
         # /* The pressure measured in hPa */
         if (($v1 eq 'NIL')||($v1 eq '////')) {
            $decoded_metar{$tr}{'hPa'}  = 'NOT AVAILABLE';
         } else {
            $decoded_metar{$tr}{'hPa'}  = int($v1);   # $tr = 'altimeter'.[nn]
            # /* The pressure measured in mmHg, inHg and atm */
            $decoded_metar{$tr}{'mmhg'} = number_format($v1 * 0.75006, 1);
            $decoded_metar{$tr}{'inhg'} = number_format($v1 * 0.02953, 2);
            $decoded_metar{$tr}{'atm'}  = number_format($v1 * 9.8692e-4, 3);
         }
         $had_dc = 1;
      } elsif ( $part =~ /^QNH([0-9]{4})INS/ ) {
         $v1 = $1;
         $tr = get_next_hr('altimeter', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Altimeter (QNH).";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'inhg'} = number_format($v1 / 100, 2);   # $tr = 'altimeter'.[nn]
         $decoded_metar{$tr}{'mmhg'} = number_format($v1 * 0.254, 2);   # 100 inch to mm
         $decoded_metar{$tr}{'hpa'}  = number_format($v1 * 0.338639, 2);
         $decoded_metar{$tr}{'atm'}  = number_format(($v1 * 3.3421e-4), 3);
         $had_dc = 1;
      } elsif ($part =~ /^T([0-9]{4}$)/) {
         $v1 = $1;
         $msg = "WARNING:2: [$msg] NOT YET HANDLED! $v1";
         prtw("$msg\n", $tt );
#   $this->store_temp($regs[1],
#           $decoded_metar['temperature']['temp_c'],
#           $decoded_metar['temperature']['temp_f']);
      } elsif ($part =~ /^1([0-9]{4}$)/) {
         $v1 = $1;
         $msg = "WARNING:3: [$msg] NOT YET HANDLED! $v1";
         prtw("$msg\n", $tt );
#         * 6 hour maximum temperature Celsius, coded to tenth of degree
#   $this->store_temp($regs[1] / 10,
#           $decoded_metar['temp_min_max']['max6h_c'],
#           $decoded_metar['temp_min_max']['max6h_f']);
      } elsif ($part =~ /^2([0-9]{4}$)/) {
         $v1 = $1;
         $msg = "WARNING:4: [$msg] NOT YET HANDLED! $v1";
         prtw("$msg\n", $tt );
#         * 6 hour minimum temperature Celsius, coded to tenth of degree
#   $this->store_temp($regs[1] / 10,
#           $decoded_metar['temp_min_max']['min6h_c'],
#           $decoded_metar['temp_min_max']['min6h_f']);
      } elsif ($part =~ /^4([0-9]{4})([0-9]{4})$/) {
         $v1 = $1;
         $msg = "WARNING:5: [$msg] NOT YET HANDLED! $v1";
         prtw("$msg\n", $tt );
#         * 24 hour maximum and minimum temperature Celsius, coded to
#         * tenth of degree
#   $this->store_temp($regs[1] / 10,
#           $decoded_metar['temp_min_max']['max24h_c'],
#           $decoded_metar['temp_min_max']['max24h_f']);
#   $this->store_temp($regs[2] / 10,
#           $decoded_metar['temp_min_max']['min24h_c'],
#           $decoded_metar['temp_min_max']['min24h_f']);
      } elsif ($part =~ /^P([0-9]{4})/) {
         $v1 = $1;
         $msg = "WARNING:6: [$msg] NOT YET HANDLED! $v1";
         prtw("$msg\n", $tt );
#         * Precipitation during last hour in hundredths of an inch
#   if ($regs[1] == '0000') {
#     $decoded_metar['precipitation']['in'] = -1;
#     $decoded_metar['precipitation']['mm'] = -1;
#   } else {
#          $decoded_metar['precipitation']['in'] =
#            number_format($regs[1]/100, 2);
#          $decoded_metar['precipitation']['mm'] =
#            number_format($regs[1]*0.254, 2);
#   }
      } elsif ($part =~ /^6([0-9]{4})/) {
         $v1 = $1;
         $tr = get_next_hr('precititation', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Precipitation during last 3 or 6 hours in hundredths of an inch.";
         $msg .= " - $tr";
         if ($v1 eq '0000') {
            $decoded_metar{$tr}{'in_6h'} = -1;
            $decoded_metar{$tr}{'mm_6h'} = -1;
         } else {
            $decoded_metar{$tr}{'in_6h'} = number_format($v1/100, 2);
            $decoded_metar{$tr}{'mm_6h'} = number_format($v1*0.254, 2);
         }
         $had_dc = 1;
      } elsif ($part =~ /^7([0-9]{4})/ ) {
         $v1 = $1;
         $msg = "WARNING:8: [$msg] NOT YET HANDLED! $v1";
         prtw("$msg\n", $tt );
#         * Precipitation during last 24 hours in hundredths of an inch.
#   if ($regs[1] == '0000') {
#     $decoded_metar['precipitation']['in_24h'] = -1;
#     $decoded_metar['precipitation']['mm_24h'] = -1;
#   } else {
#          $decoded_metar['precipitation']['in_24h'] =
#            number_format($regs[1]/100, 2, '.', '');
#          $decoded_metar['precipitation']['mm_24h'] =
#            number_format($regs[1]*0.254, 2, '.', '');
#   }
      } elsif ($part =~ /^4\/([0-9]{3})/) {
         $v1 = $1;
         $msg = "WARNING:9: [$msg] NOT YET HANDLED! $v1";
         prtw("$msg\n", $tt );
#         * Snow depth in inches
#   if ($regs[1] == '0000') {
#     $decoded_metar['precipitation']['snow_in'] = -1;
#     $decoded_metar['precipitation']['snow_mm'] = -1;
#   } else {
#     $decoded_metar['precipitation']['snow_in'] = $regs[1] * 1;
#     $decoded_metar['precipitation']['snow_mm'] = round($regs[1] * 25.4);
#   }
      } elsif ($part =~ /^PROB([0-9]{1,2})$/) {
         $v1 = $1;
         $tr = get_next_hr('probability', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Probability.";
         $msg .= " - $tr";
         # PROB40 2022 Probability. The probability of a weather event occurring is given in percent
         # along with the 2-digit Zulu hour start and end of the event. 
         $decoded_metar{$tr}{'percent'} = $v1;
         if ($i2 < $num_parts) {
            $part = $parts[$i+1];
            if ($part =~ /^([0-9]{2})([0-9]{2})$/) {
               $v1 = $1;
               $v2 = $2;
               $decoded_metar{$tr}{'start'} = $v1;
               $decoded_metar{$tr}{'end'} = $v2;
               $msg .= " (start=$v1, end=$v2)";
               $i = $i2;
            }
         }
         $had_dc = 1;
      } elsif ($part =~ /^[MP]?(([0-9]?)[ ]?([0-9])(\/?)([0-9]*))SM$/ ) {
         # Examples:
         # 1/2SM - Visibility one-half statute mile 
         # 2 1/4SM - Visibility two and one-quarter statute miles 
         # 5SM - Visibility five statute miles 
         # P6SM - Visibility more than six statute miles  
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $v4 = $4;
         $tr = get_next_hr('visibility', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Visibility.(v1-4:";
         $msg .= ($v1 && length($v1)) ? $v1 : '-';
         $msg .= ',';
         $msg .= ($v2 && length($v2)) ? $v2 : '-';
         $msg .= ',';
         $msg .= ($v3 && length($v3)) ? $v3 : '-';
         $msg .= ',';
         $msg .= ($v4 && length($v4)) ? $v4 : '-';
         $msg .= ')';
         $msg .= " - $tr";
         $decoded_metar{$tr}{'statute-miles'} = $v1;
         $had_dc = 1;
      } elsif ($part =~ /([0-9]{2})([0-9]{2})([0-9]{2})/) {
         $v1 = $1;
         $v2 = $2;
         $v3 = $3;
         $tr = get_next_hr('time-range', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Time Range";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'day'} = $v1;
         $decoded_metar{$tr}{'begin-hour'} = $v2;
         $decoded_metar{$tr}{'end-hour'} = $v3;
         $had_dc = 1;
      } elsif ($part =~ /^FM([0-9]{2})([0-9]{2})/) {
         $v1 = $1;
         $v2 = $2;
         # eg FM1930 = FroM and 2-digit hour and 2-digit minute
         # beginning time: indicates significant change.
         # Each FM starts on a new line, indented 5 spaces.
         $tr = get_next_hr('time-range', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Time Range FroM(2:2)";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'begin-hour'} = $v1;
         $decoded_metar{$tr}{'begin-mins'} = $v2;
         $had_dc = 1;
      } elsif ($part =~ /^FM([0-9]{2})$/) {
         # eg FM19 = FroM and 2-digit hour assume 00 minute
         # beginning time: indicates significant change.
         # Each FM starts on a new line, indented 5 spaces.
         $v1 = $1;
         $v2 = '00';
         $tr = get_next_hr('time-range', \%decoded_metar);
         $tr = "$cord.$tr" if ($addorder);
         $msg .= " - Time Range FroM(2)";
         $msg .= " - $tr";
         $decoded_metar{$tr}{'begin-hour'} = $v1;
         $decoded_metar{$tr}{'begin-mins'} = $v2;
         $had_dc = 1;
      } else {
         # If we couldn't match the group, so we could assume that it was a remark.
         # $decoded_metar['remarks'] .= ' ' . $part;
         if ($part eq 'T') {
            #prt( "Processing a 'T' ...\n" );
            while ($i2 < $num_parts) {
               my $tmppt = $parts[$i2];
               #prt( "Processing a [$tmppt] ...\n" );
               if ($tmppt =~ /^[0-9]{2}$/) {
                  #prt( "Adding [$tmppt] to [$part]...\n" );
                  $part .= $tmppt;
                  if ($part =~ /^T([0-9]{4})([0-9]{4})/ ) {
                     #prt( "Got [$part] $1 $2 ...\n" );
                     $i = $i2;
                     last;
                  }
                  $i2++;
               } else {
                  last;
               }
            }
            if ($part =~ /^T([0-9]{4})([0-9]{4})/ ) {
               $v1 = $1 / 10;
               $v2 = $2 / 10;
               $tr = get_next_hr('temperature', \%decoded_metar);
               $tr = "$cord.$tr" if ($addorder);
               $msg = "$part - Temperature/Dewpoint - $v1, $v2";
               $msg .= " - $tr";
               $v3 = '';
               if ($v1 && length($v1) && (substr($v1,0,1) eq 'M')) {
                  $v3 = '-';
               }
               $decoded_metar{$tr}{'temp_c'} = $v3.int($v1/100);
               $decoded_metar{$tr}{'temp_f'} = $v3.int( (($v1/100) * (9/5)) + 32.5 );
               # The dewpoint could be missing, this is indicated by the
               # second group being empty at most places, but in the UK they
               # use '//' instead of the missing temperature... */
               $v3 = '';
               if ($v2 && length($v2) && (substr($v2,0,1) eq 'M')) {
                  $v3 = '-';
               }
               if (length($v2) && ($v2 ne '//')) {
                  $decoded_metar{$tr}{'dew_c'} = $v3.int($v2/100);
                  $decoded_metar{$tr}{'dew_f'} = $v3.int((($v2/100) * (9/5)) + 32.5);
               }
               $had_dc = 1;
               $part = '';
            }
         }
         # to handle
         # Q 1007 1009 1008 1006
         if ($part eq 'Q') {
            #prt( "Processing a 'T' ...\n" );
            while ($i2 < $num_parts) {
               $npart = $parts[$i2];
               if ($npart =~ /\d{4}/) {
                  $v1 = $npart;
                  $tr = get_next_hr('altimeter', \%decoded_metar);
                  $tr = "$cord.$tr" if ($addorder);
                  $msg .= " - Altimeter (Qsp).";
                  $msg .= " - $tr";
                  # /* The pressure measured in hPa */
                  $decoded_metar{$tr}{'hpa'}  = int($v1);   # $tr = 'altimeter'.[nn]
                  # /* The pressure measured in mmHg, inHg and atm */
                  $decoded_metar{$tr}{'mmhg'} = number_format($v1 * 0.75006, 1);
                  $decoded_metar{$tr}{'inhg'} = number_format($v1 * 0.02953, 2);
                  $decoded_metar{$tr}{'atm'}  = number_format($v1 * 9.8692e-4, 3);
                  $i = $i2;
                  $i2 = $i + 1;
               } else {
                  last;
               }
            }
            $had_dc = 1;
            $part = '';
         }
         if (length($part) && (substr($part,0,1) eq '(') ) {
            # begin bracket open - go until closed
            $npart = $part;
            if ( !($npart =~ /\)$/) ) {
               while ($i2 < $num_parts) {
                  $npart .= ' '.$parts[$i2];
                  if ($npart =~ /\)$/) {
                     last;
                  }
                  $i2++;
               }
            }
            if ( $npart =~ /\)$/) {
               $tr = get_next_hr('brackets', \%decoded_metar);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= " - $npart";
               $msg .= " - $tr";
               $decoded_metar{$tr} = $npart;   # $tr = 'brackets'.[nn]
               $i = $i2;
               $part = '';
            }
         }
         if (length($part)) {
            $npart = get_acro_desc($part);
            if ($npart ne 'NONE') {
               $tr = get_next_hr('acronym', \%decoded_metar);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= " - $npart";
               $msg .= " - $tr";
               $decoded_metar{$tr}{$part} = $npart;   # $tr = 'acronym'.[nn]
               #while ($i2 < $num_parts) {
               #   $part = $parts[$i2];
               #}
               $msg .= " - acronym for $npart - $tr";
               $part = '';
            }
         }
         if (length($part) && ( is_in_taf_exceptions($part) || is_in_taf_spl_exceptions($part)  || (length($part) == 1) ) ) {
            $excnum++;
            $tr = 'exception';
            $tr = "998.$tr" if ($addorder);
            $decoded_metar{$tr}{$excnum} = $part;
            $msg .= " - $tr:$excnum";
            $had_z = 0 if ($part eq 'BY');   # EXCEPTION SEEN - BY ETGL
            $part = '';
         }
         if (length($part) && ($part =~ /^NSW$/)) {
            # NWS TAFs exclude turbulence, icing & temperature forecasts;
            # NWS METARs exclude trend fcsts 
            $tr = get_next_hr('nsw', \%decoded_metar);
            $tr = "$cord.$tr" if ($addorder);
            $decoded_metar{$tr} = 'exclude trend fcst';
            $msg .= " - $tr";
            $part = '';
            $had_dc = 1;
         }
         if ($part =~ /^[A-Z]{5}$/) {
            $v1 = $part;
            # $decoded_metar{'icao'}  = $part;
            $tr = get_next_hr('icao', \%decoded_metar);
            $tr = "$cord.$tr" if ($addorder);
            $msg .= " - Station Identifier(5).";
            $msg .= " - $tr";
            $decoded_metar{$tr} = "$v1 (CHECKME)";   # $tr = 'icao'.[nn]
            $part = '';
            $had_dc = 1;
         }
         if ($part =~ /^(\w{4})\/(\w{4})/) {
            my @arr = split(/\//,$part);
            $msg .= ' - Station Ident (mult)';
            foreach $npart (@arr) {
               $tr = get_next_hr('icao', \%decoded_metar);
               $tr = "$cord.$tr" if ($addorder);
               $msg .= " - $npart";
               $msg .= " - $tr";
               $decoded_metar{$tr} = $npart;   # $tr = 'icao'.[nn]
            }
            $part = '';
            $had_dc = 1;
         }
         ############################
         if (length($part)) {
            $tr = 'UNDECODED';
            $tr = "999.$tr" if ($addorder);
            $msg = "WARNING:ELSE: [$msg] *** NO CASE FOR THIS! ***";
            $msg .= " - $tr";
            prtw("$msg\n", $tt );
            if ($part =~ /^[A-Z]{5,9}$/) {
               add_2_exceptions($part);
            }
            if (defined $decoded_metar{$tr}) {
               $decoded_metar{$tr} .= ' '.$part;
            } else {
               $decoded_metar{$tr} = $part;
            }
         }
      }
      prt( "$msg\n" ) if ($verbose);
   }
   if (no_icao_found(\%decoded_metar)) {
      $msg = "WARNING:ICAO: NO ICAO FOUND IN THIS METAR/TAF!";
      prtw("$msg\n", $tt );
   }
   return %decoded_metar;
}
#################################################
sub load_airport_file {
   my ($aptdat) = shift;   # = $APTFILE;
   my ($line, $alat, $alon, $icao, $name, $rlat, $rlon);
   my (@arr, @arr2);
   prt("\nLoading $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, "gzip -d -c $aptdat|" or mydie( "ERROR: CAN NOT OPEN $aptdat...$!...\n" );
   my @lines = <IF>;
   close IF;
   my $cnt = scalar @lines;
   prt( "Processing $cnt lines ...\n" );
   my $apt = '';
   my $rwycnt = 0;
   my $glat = 0;
   my $glon = 0;
   my $totaptcnt = 0;   # count another AIRPORT
   foreach $line (@lines) {
      $line = trim_all($line);
      ###prt("$line\n");
      @arr = split(/ /,$line);
      if ($line =~ /^$aln\s+/) {   # start with '1'
         if (length($apt) && ($rwycnt > 0)) {
            $alat = $glat / $rwycnt;
            $alon = $glon / $rwycnt;
            @arr2 = split(/ /,$apt);
            $icao = $arr2[4];
            $name = join(' ', splice(@arr2,5));
            push(@aptlist, [$icao, $name, $alat, $alon]);
         }
         $apt = $line;
         $rwycnt = 0;
         $glat = 0;
         $glon = 0;
         $totaptcnt++;   # count another AIRPORT
      } elsif ($line =~ /^$rln\s+/) {
         $rlat = $arr[1];
         $rlon = $arr[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" );
         last;
      }
   }
         if (length($apt) && ($rwycnt > 0)) {
            $alat = $glat / $rwycnt;
            $alon = $glon / $rwycnt;
            @arr2 = split(/ /,$apt);
            $icao = $arr2[4];
            $name = join(' ', splice(@arr2,5));
            push(@aptlist, [$icao, $name, $alat, $alon]);
         }
}
sub get_fix_len {
   my ($d) = shift;
   my $stg = sprintf("%03.7f", $d);
   my @arr = split(/\./,$stg);
   if (scalar @arr == 2) {
      $arr[0] = ' '.$arr[0] while ( length($arr[0]) < 4 );
      $stg = $arr[0].'.'.$arr[1];
   }
   return $stg;
}
sub show_airport {
   my ($off) = shift;
   # push(@aptlist, [$icao, $name, $alat, $alon]);
   my $cnt = scalar @aptlist;
   if (($off > 0) && ($off <= $cnt)) {
      $off--;   # back up to logical
      my $icao = $aptlist[$off][0];
      $icao .= ' ' while (length($icao) < 4);
      my $name = $aptlist[$off][1];
      my $alat = $aptlist[$off][2];
      my $alon = $aptlist[$off][3];
      my $slat = get_fix_len($alat);
      my $slon = get_fix_len($alon);
      prt( "$icao $slat $slon $name\n" );
   }
}
# references viewed
# from : http://aviationweather.gov/static/help/taf-decode.php - see html/taf-decode.htm
# A TAF report contains the following sequence of elements in the following order:
# 1. Type of Report 
# 2. ICAO Station Identifier 
# 3. Date and Time of Origin 
# 4. Valid Period Date and Time 
# 5. Forecast Meteorological Conditions 
# from : http://mbev.net/wikka/METARSandTAFS
# KJAX 020256Z 02003KT 10SM TSRA OVC01OCB SCT100 BKN130 18/17 A2996 
# METAR's will always be published in the same order: see: html/METARSandTAFS.htm
# see from : http://www.nws.noaa.gov/oso/oso1/oso12/document/guide.shtml
# also from : http://www.faa.gov/about/office_org/field_offices/fsdo/orl/local_more/media/ppt/metar.ppt
# from : http://www.srh.noaa.gov/srh/cwwd/msd/note6.html
# from : http://www.wunderground.com/metarFAQ.asp
# general ORDER seems to be -
# TYPE  ID   TIME    WIND       VIS WX    SKY    T/TD  ALT   REMARK 
# METAR KORD 041656Z 19020G26KT 6SM -SHRA BKN070 12/08 A3016 RMK AO2 
# from : http://www.nws.noaa.gov/oso/oso1/oso12/overview.htm
# from : http://www.flyingineurope.be/metar_taf_decode.htm
# from : http://www.alaska.faa.gov/fai/afss/metar%20taf/metcont.htm
# from : http://www.geocities.com/CapeCanaveral/Lab/6799/metarpg.htm
# from : http://www.alaska.faa.gov/fai/afss/metar%20taf/sametar1.htm
# from : http://weather.cod.edu/notes/metar.html
# from : http://aviation.weathersa.co.za/codesexpl.php
# a) Code form.
#     Add “AMD” and “COR” after “TAF” (before ICAO ID);
#     Add “NIL” or “CNL” after  “Y1Y1G1G1G2G2”.
#     Reason: there is an aeronautical requirement to identify amended, corrected, missing
#     and cancelled aerodrome forecasts (Amendment 72 and draft Amendment 73 to Annex 3);
# a) 51.1.1 Amend to read as follows: “The code name TAF shall be included at the beginning
#     of each individual aerodrome forecast. (Amendment 72 to Annex 3);
#  AMD = Amended  COR = Corrected  CNL = Cancelled            Aviation Code Manual    
# from : http://www.pprune.org/forums/showthread.php?s=822dc56eacab626fce1992990baa3247&p=3732431#post3732431
# http://aviation.weathersa.co.za/codesexpl.php
# I think TX is maximum temp, and TN is minimum temp forecast
# UK - You need a login (it is free)
# login - http://secure.metoffice.com/logon.jsp
# from : http://www.met.tamu.edu/Weather_Interface/
# can fetch METAR/TAF for 3-digit USA codes - FSO
# KSFO 291756Z 13003KT 10SM -RA SCT027 OVC034 07/04 A3031 RMK AO2 RAB52 SLP262 P0001 60001 T00720044 10072 20044 53010=
# The weather observed at SAN FRANCISCO, CA (KSFO) at 09:56 AM PST was:
#  The skies were cloudy. = YES
#  The weather reported was light rain. = YES
#  Temperature: 45F (  7C)  Dewpoint:  40F (  4C)  Relative Humidity:  82%
#  Winds from the SE (130 degs) at  3 mph.
#  Pressure: 1026.2 millibars.  Altimeter:30.31 inches of mercury. = YES
#  The prevailing visibility was 10 miles. = YES
#  The maximum temperature in the past 6 hours was  45F.
#  The minimum temperature in the past 6 hours was  40F.
#  There was 0.01 inches of precipitation in the past 6 hours.
# MY DECODE
#- icao .........: KSFO
#- time .........: mins=56, hour=17, day=29 = ERROR!!!
#- wind .........: miles_per_hour=3.4, meters_per_second=1.5, knots=03, deg=130 = OK
#- visibility ...: statute-miles=10 = OK
#- weather ......: proximity=at aerodrome, obscuration=RA (Rain), precipitation=RA (Rain), intensity=light (-) = OK
#- clouds .......: ft=2700, meter=822 = OK
#- clouds1 ......: ft=3400, meter=1036 = OK
#- temperature ..: dew_f=39, temp_f=45, temp_c=7, dew_c=4 MISSING Rel.Hum: 82%
#- altimeter ....: inhg=30.31, mmhg=769.8, atm=1.012, hpa=1026 = OK
#- remarks ......: RMK AO2 RAB52 SLP262 P0001 60001 T00720044 10072 20044 53010
# To CALCULATE RELATIVE HUMIDITY from TEMPERATURE AND DUE POINT
# function run_dp(IncomingForm) {
# t= 0.0; td=0.0; e= 0.0; es=0.0;
# CheckChar(IncomingForm); CheckChar1(IncomingForm);
# t = parseFloat(x); td = parseFloat(y);
# if (t<td) { alert("Temp less than Dewpoint, please check values and retry"); } else {
# e = Math.exp((17.269 * td) / (237.3 + td));
# es= Math.exp((17.269 * t) / (237.3 + t));
# if (es!=0){ z = e * 100 / es;
# if (z > 100.0) z =100;
# if (z < 0) z=0;
# z = Math.round(z);
# document.inputform.rh.value = z;} } }
sub get_relative_humidity_f {
   my ($ta, $td) = @_;
   if ($ta < $td) {
      return "T < DP! ($ta < $td)";
   }
   my $e = (17.269 * $td) / (237.3 + $td);
   my $es = (17.269 * $ta) / (237.3 + $ta);
   if ($es != 0) {
      my $z = ($e * 100) / $es;
      $z = 100 if ($z > 100);
      $z = 0 if ($z < 0);
      return $z;
   }
   return 'failed';
}
# from : http://www.luftpiraten.de/mtd.html
# EDDF 250450Z 18004KT 4800 BR SCT018 BKN032 00/00 Q1014 8829//95 NOSIG
# METAR Report für: Frankfurt, Deutschland (Rhein Main) - EDDF/FRA
# Beobachtungszeit: 25.xx.xxxx 04:50 Uhr UTC
#Wind ...........: aus 180° mit 4 kt = YES
#Sicht ..........: 4800 m
#Wetter .........: feuchter Dunst 
#Wolken .........: 3/8 - 4/8 in 1800 ft über Flugplatzniveau
#                  5/8 - 7/8 in 3200 ft über Flugplatzniveau
#Temperatur .....: 00°C
#Taupunkt .......: 00°C
#QNH ............: 1014 hPa = YES
#Pistenzustand...: alle Pisten: naß oder Wasserpfützen, 51% bis 100% bedeckt, 
#                  Höhe der Ablagerungen betrieblich nicht signifikant oder
#                  nicht meßbar, gute Bremswirkung
#                  keine wesentliche Änderung erwartet
#- icao .........: EDDF
#- time .........: mins=50, hour=04, day=25
#- wind .........: miles_per_hour=4.6, meters_per_second=2, knots=04, deg=180 = OK
#- visibility ...: km=4.8, ft=15748, meter=4800, mile=2.9, prefix=0
#- weather ......: proximity=at aerodrome, obscuration=BR (Mist >= 5/8SM), precipitation=BR (CHECKME), intensity=moderate (no sign)
#- clouds .......: ft=1800, meter=548
#- clouds1 ......: ft=3200, meter=975
#- temperature ..: relative_humidity=100% appx., dew_f=32, temp_f=32, temp_c=0, dew_c=0
#- altimeter ....: inhg=29.94, mmhg=760.5, atm=1, hpa=1014 = OK
#- icao1 ........: NOSIG
#- UNDECODED ....: 8829//95
# from : http://adds.aviationweather.gov/tafs/ - fetches and decodes METAR/TAF
# KSFO 301456Z 29003KT 10SM FEW015 SCT075 07/04 A3032 RMK AO2 SLP266 60000 T00670039 53009
# KSFO 301303Z 301312 27005KT P6SM VCSH SCT070 OVC090 
#     TEMPO 1317 5SM BR BKN040 
#     FM1900 29010G15KT P6SM FEW100 
#     FM0200 28010KT P6SM SCT080 
#     FM0900 17004KT P6SM SCT010 OVC060
#Aviation Digital Data Service (ADDS)
#Output produced by TAFs form (1503 UTC 30 January 2008)
#found at http://adds.aviationweather.gov/tafs/index.php
#METAR text:  KSFO 301456Z 29003KT 10SM FEW015 SCT075 07/04 A3032 RMK AO2 SLP266 60000 T00670039 53009  
#Conditions at:  KSFO (SAN FRANCISCO, CA, US) observed 1456 UTC 30 January 2008  
#Temperature:  6.7°C (44°F)  
#Dewpoint:  3.9°C (39°F) [RH = 82%]  
#Pressure (altimeter):  30.32 inches Hg (1026.8 mb)
#[Sea-level pressure: 1026.6 mb]  
#Winds:  from the WNW (290 degrees) at 3 MPH (3 knots; 1.6 m/s)  
#Visibility:  10 or more miles (16+ km)  
#Ceiling:  at least 12,000 feet AGL  
#Clouds:  few clouds at 1500 feet AGL
#scattered clouds at 7500 feet AGL  
#Weather:  no significant weather observed at this time  
#--------------------------------------------------------------------------------
#Forecast for:  KSFO (SAN FRANCISCO, CA, US)  
#Text:  KSFO 301303Z 301312 27005KT P6SM VCSH SCT070 OVC090  
#Forecast period:  1300 to 1900 UTC 30 January 2008  
#Forecast type:  FROM: standard forecast or significant change  
#Winds:  from the W (270 degrees) at 6 MPH (5 knots; 2.6 m/s)  
#Visibility:  6 or more miles (10+ km)  
#Ceiling:  9000 feet AGL  
#Clouds:  scattered clouds at 7000 feet AGL
#overcast cloud deck at 9000 feet AGL  
#Weather:  VCSH  (showers in vicinity)  
#Text:  TEMPO 1317 5SM BR BKN040  
#Forecast period:  1300 to 1700 UTC 30 January 2008  
#Forecast type:  TEMPORARY: The following changes expected for less than half the time period  
#Visibility:  5 miles (8 km)  
#Ceiling:  4000 feet AGL  
#Clouds:  broken clouds at 4000 feet AGL  
#Weather:  BR  (mist)  
#Text:  FM1900 29010G15KT P6SM FEW100  
#Forecast period:  1900 UTC 30 January 2008 to 0200 UTC 31 January 2008  
#Forecast type:  FROM: standard forecast or significant change  
#Winds:  from the WNW (290 degrees) at 12 MPH (10 knots; 5.2 m/s)
#gusting to 17 MPH (15 knots; 7.8 m/s)  
#Visibility:  6 or more miles (10+ km)  
#Clouds:  few clouds at 10000 feet AGL  
#Weather:  no significant weather forecast for this period  
#Text:  FM0200 28010KT P6SM SCT080  
#Forecast period:  0200 to 0900 UTC 31 January 2008  
#Forecast type:  FROM: standard forecast or significant change  
#Winds:  from the W (280 degrees) at 12 MPH (10 knots; 5.2 m/s)  
#Visibility:  6 or more miles (10+ km)  
#Clouds:  scattered clouds at 8000 feet AGL  
#Weather:  no significant weather forecast for this period  
#Text:  FM0900 17004KT P6SM SCT010 OVC060  
#Forecast period:  0900 to 1200 UTC 31 January 2008  
#Forecast type:  FROM: standard forecast or significant change  
#Winds:  from the S (170 degrees) at 5 MPH (4 knots; 2.1 m/s)  
#Visibility:  6 or more miles (10+ km)  
#Ceiling:  6000 feet AGL  
#Clouds:  scattered clouds at 1000 feet AGL
#overcast cloud deck at 6000 feet AGL  
#Weather:  no significant weather forecast for this period  
#--------------------------------------------------------------------------------
# from : http://www.skystef.be/metar-decoder.htm
# paste or copy METAR / TAF and get decode
# eg
# KSFO 301303Z 301312 27005KT P6SM VCSH SCT070 OVC090 
#     TEMPO 1317 5SM BR BKN040 
#     FM1900 29010G15KT P6SM FEW100 
#     FM0200 28010KT P6SM SCT080 
#     FM0900 17004KT P6SM SCT010 OVC060
# gives
#Location...........: KSFO
#Day of month.......: 30
#Time...............: 13:03 UTC
#Wind...............: true direction = 270 degrees; speed = 5 knots
#Weather............: in the vicinity shower(s) of 
#Cloud coverage.....: scattered (3 to 4 oktas) at 7000 feet above aerodrome level
#Cloud coverage.....: overcast (8 oktas) at 9000 feet above aerodrome level
#Next 2hrs temporary:
#Visibility.........: 1317 meter
#Weather............: mist 
#Cloud coverage.....: broken (5 to 7 oktas) at 4000 feet above aerodrome level
#From 19:00 UTC.....:
#Wind...............: true direction = 290 degrees; speed = 10 knots with gusts of 15 knots
#Cloud coverage.....: few (1 to 2 oktas) at 10000 feet above aerodrome level
#From 02:00 UTC.....:
#Wind...............: true direction = 280 degrees; speed = 10 knots
#Cloud coverage.....: scattered (3 to 4 oktas) at 8000 feet above aerodrome level
#From 09:00 UTC.....:
#Wind...............: true direction = 170 degrees; speed = 4 knots
#Cloud coverage.....: scattered (3 to 4 oktas) at 1000 feet above aerodrome level
# *AND*
#EDDF 250450Z 18004KT 4800 BR SCT018 BKN032 00/00 Q1014 8829//95 NOSIG
#Location...........: EDDF
#Day of month.......: 25
#Time...............: 04:50 UTC
#Wind...............: true direction = 180 degrees; speed = 4 knots
#Visibility.........: 4800 meter
#Weather............: mist 
#Cloud coverage.....: scattered (3 to 4 oktas) at 1800 feet above aerodrome level
#Cloud coverage.....: broken (5 to 7 oktas) at 3200 feet above aerodrome level
#Temperature........: 00 degrees Celsius
#Dewpoint...........: 00 degrees Celsius
#QNH (msl pressure).: 1014 hectopascal
#Next 2 hours.......: no significant changes
#- icao .........: EDDF
#- time .........: mins=50, hour=04, day=25
#- wind .........: miles_per_hour=4.6, meters_per_second=2, knots=04, deg=180
#- visibility ...: km=4.8, ft=15748, meter=4800, mile=2.9, prefix=0
#- weather ......: proximity=at aerodrome, obscuration=BR (Mist >= 5/8SM), precipitation=BR (CHECKME), intensity=moderate (no sign)
#- clouds .......: ft=1800, meter=548, conditions=SCT scattered (3-4 oktas)
#- clouds1 ......: ft=3200, meter=975, conditions=BKN broken (5-7 oktas)
#- temperature ..: relative_humidity=100% appx., dew_f=32, temp_f=32, temp_c=0, dew_c=0
#- altimeter ....: inhg=29.94, mmhg=760.5, atm=1, hpa=1014
#- icao1 ........: NOSIG
#- UNDECODED ....: 8829//95
# *AND*
# LIRL 121100Z 12221 17007KT 9999 SCT018 SCT070 TEMPO 1218 FEW020CB
#Location...........: LIRL
#Day of month.......: 12
#Time...............: 11:00 UTC
#Wind...............: true direction = 170 degrees; speed = 7 knots
#Visibility.........: 10 kilometers or more
#Cloud coverage.....: scattered (3 to 4 oktas) at 1800 feet above aerodrome level
#Cloud coverage.....: scattered (3 to 4 oktas) at 7000 feet above aerodrome level
#Next 2hrs temporary:
#Visibility.........: 1218 meter
#Cloud coverage.....: few (1 to 2 oktas) at 2000 feet above aerodrome level cumulonimbus
# *AND*
# YSSY 301644Z 301818 35008KT 9999 FEW012 FM23 04015KT 9999 FEW040 FM08 18020G30KT 9999 -SHRA SCT012 BKN020 PROB30 INTER 0212 3000 TSRA SCT015 SCT050CB INTER 1218 4000 SHRA BKN010 
#Location...........: YSSY
#Day of month.......: 30
#Time...............: 16:44 UTC
#Wind...............: true direction = 350 degrees; speed = 8 knots
#Visibility.........: 10 kilometers or more
#Cloud coverage.....: few (1 to 2 oktas) at 1200 feet above aerodrome level
#Wind...............: true direction = 040 degrees; speed = 15 knots
#Visibility.........: 10 kilometers or more
#Cloud coverage.....: few (1 to 2 oktas) at 4000 feet above aerodrome level
#Wind...............: true direction = 180 degrees; speed = 20 knots with gusts of 30 knots
#Visibility.........: 10 kilometers or more
#Weather............: light shower(s) of rain 
#Cloud coverage.....: scattered (3 to 4 oktas) at 1200 feet above aerodrome level
#Cloud coverage.....: broken (5 to 7 oktas) at 2000 feet above aerodrome level
#Visibility.........: 212 meter
#Visibility.........: 3000 meter
#Weather............: thunderstorm rain 
#Cloud coverage.....: scattered (3 to 4 oktas) at 1500 feet above aerodrome level
#Cloud coverage.....: scattered (3 to 4 oktas) at 5000 feet above aerodrome level cumulonimbus
#Visibility.........: 1218 meter
#Visibility.........: 4000 meter
#Weather............: shower(s) of rain 
#Cloud coverage.....: broken (5 to 7 oktas) at 1000 feet above aerodrome level
# and mine
#- icao .........: YSSY
#- time .........: mins=44, hour=16, day2=30, day=30, end-hour=18, begin-hour=18
#- wind .........: miles_per_hour=9.2, meters_per_second=4.1, knots=08, deg=350
#- visibility ...: km=10, ft=32800, meter=10000, mile=6.2, prefix=1
#- clouds .......: ft=1200, meter=365, conditions=FEW few clouds (1-2 oktas)
#- time-range ...: begin-mins=00, begin-hour=23
#- wind1 ........: miles_per_hour=17.2, meters_per_second=7.7, knots=15, deg=040
#- visibility1 ..: km=10, ft=32800, meter=10000, mile=6.2, prefix=1
#- clouds1 ......: ft=4000, meter=1219, conditions=FEW few clouds (1-2 oktas)
#- time-range1 ..: begin-mins=00, begin-hour=08
#- wind2 ........: miles_per_hour=23, meters_per_second=10.2, knots=20, deg=180
#- visibility2 ..: km=10, ft=32800, meter=10000, mile=6.2, prefix=1
#- weather ......: proximity=at aerodrome, obscuration=RA (Rain), precipitation=RA (Rain), intensity=light (-), decriptor=SH (Showers)
#- clouds2 ......: ft=1200, meter=365, conditions=SCT scattered (3-4 oktas)
#- clouds3 ......: ft=2000, meter=609, conditions=BKN broken (5-7 oktas)
#- probability ..: percent=30
#- remarks ......: INTER 0212 3000 TSRA SCT015 SCT050CB INTER 1218 4000 SHRA BKN010
# from : http://www.astro.keele.ac.uk/oldusers/rno/Aviation/metar_codes.html
# eof - geturl02.pl

index -|- top

checked by tidy  Valid HTML 4.01 Transitional