#!/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
