#!/usr/bin/perl -w
########################################################################
#
# filename:	metar2text
#
# description:	A PERL program that will read a METAR from the STDIN and
#               produce a human readable report. Multiple languages are
#		supported by having all language dependent information
#		in a seperate file. Thus that is the only files that must
#		be changed to support a different language. The main 
#		engine contained here does not need to be modified.
#
#		One advantage of this program over GEO:METAR is that 
#		program allows international information to be processed
#		and this one provides a much more comprehensive report.
#		If you want to create your own report, ALL of the 
#		of the decoded data is available
#
# formulas:	Wind Chill - T = temp F; V = wind speed MPH
#		Valid for V >= 3 and T <= 45
#		WC = 35.74 + 0.6215T - 35.75(V^0.16) + 0.4275T(V^0.16)
#
#		Heat Index - T = temp F; RH = Relative Humidity
#		Valid for T >= 80 and RH >= 40
#		HI = -42.379 + 2.0491523T + 10.14333127RH -0.22475541TRH
#		- (6.83783 * 10^-3)T^2 - (5.48171 * 10^-2)RH^2
#		+ (1.22874 * 10^-3)T^2RH + (8.5282 * 10^-4)TRH^2
#		- (1.99 * 10^-6)T^2RH^2
#
#		Relative Humidity - T = temp C; Td = dew point C
#		RH = (((112 - (0.1T) + Td) / (112 + (0.9T)))**8)*100.0
#
# history:
# 20040818	kc6hur	Alpha release
# 20060903	kc6hur	0.10	New formulas for Relative Humidity,
#				Wind Chill and added Heat Index. Provide
#				better personalization of report.
#
# $Id: metar2text 31 2009-01-05 02:52:02Z  $"
########################################################################

package METAR;

## Required Modules

use 5.005;
use strict;
use vars qw($AUTOLOAD $VERSION);
use Carp 'cluck';
use Date::Manip qw(ParseDate UnixDate);
use Time::Local;
# use exported CALL_SPACED variable (from wx_scripts.conf)
use Env qw(CALL_SPACED);

my $VERSION = '0.10';

##
## Language dependent data
##

########################################################################
## These tables contain all of the "words" used to generate the final 
## printed report. In order to provide, international support, all that 
## is necessary is to change the data in these tables.
########################################################################

my %_weather_types = (
    MI => ' shallow',
    PR => ' partial',
    BC => ' patches',
    DR => ' low drifting',	## drizzle
    BL => ' blowing',
    SH => ' shower(s)',
    TS => ' thunderstorm',
    FZ => ' freezing',

    DZ => ' drizzle',
    RA => ' rain',
    SN => ' snow',
    SG => ' snow grains',
    IC => ' ice crystals',
    PE => ' ice pellets',
    GR => ' hail',
    GS => ' small hail and or snow pellets',
    UP => ' unknown',

    BR => ' mist',
    FG => ' fog',
    FU => ' smoke',
    VA => ' volcanic ash',
    DU => ' widespread dust',		## dust
    SA => ' sand',
    HZ => ' haze',
    PY => ' spray',

    PO => ' well-developed dust sand whirls',
    SQ => ' squalls',
    FC => ' funnel cloud (tornado/waterspout)',
    SS => ' sand storm',
    DS => ' dust storm'
);

my %_cloud_condition = (
    SKC => 'was clear',
    CLR => 'was clear',
    VV  => 'was vertical visibility',
    FEW => 'were a few',
    SCT => 'were scattered',
    BKN => 'were broken',
    OVC => 'was overcast',
);

my %_cloud_coverage = (
    SKC => '0',
    CLR => '0',
    VV  => '8/8',
    FEW => '1/8 - 2/8',
    SCT => '3/8 - 4/8',
    BKN => '5/7 - 7/8',
    OVC => '8/8',
);

my @_wind_dir = (
    'North',
    'North Northeast',
    'Northeast',
    'East Northeast',
    'East',
    'East Southeast',
    'Southeast',
    'South Southeast',
    'South',
    'South Southwest',
    'Southwest',
    'West Southwest',
    'West',
    'West Northwest',
    'Northwest',
    'North Northwest',
    'North');

my %_units = (
    english_height		=> 'feet',
    english_depth		=> 'inches',
    english_distance		=> 'miles',
    english_speed		=> 'miles per hour',
    english_pressure		=> 'inches of mercury',
    english_temperature		=> 'degrees',
    metric_height		=> 'meters',
    metric_depth		=> 'millimeters',
    metric_distance		=> 'kilometers', 
    metric_speed		=> 'meters per second',
    metric_pressure		=> 'hecto-pascals',
    metric_temperature		=> 'celcius',
);

my %_strings = (
    no_data               => 'Sorry! There is no data available for %s.',
    mm_inches             => '%s %s',
    precip_a_trace        => 'a trace',
    precip_there_was      => ' There was %s of precipitation ',
    sky_str_format1       => ' There %s at a height of %s %s',
    sky_str_format2       => ', %s at a height of %s %s and %s at a height of %s %s',
    sky_str_format3       => ' and %s at a height of %s %s',
    sky_str_clear         => ' The sky was clear',
    sky_cavok             => ' There were no clouds below %s.',
    clouds                => ' clouds',
    clouds_cb             => ' cumulonimbus clouds',
    clouds_tcu            => ' towering cumulus clouds',
    visibility_format     => ' The visibility was %s %s.',
    wind_str_format1      => 'blowing at a speed of %s %s',
    wind_str_format2      => ', with gusts to %s %s,',
    wind_str_format3      => ' from %s',
    wind_str_calm         => 'calm',
    wind_vrb_long         => 'variable directions',
    windchill             => ', the windchill was %s %s',
    rel_humidity          => ' The relative humidity was %s%%.',
    heat_index            => ', the heat index was %s %s',
    precip_last_hour      => 'in the last hour. ',
    precip_last_6_hours   => 'in the last 3 to 6 hours. ',
    precip_last_24_hours  => 'in the last 24 hours. ',
    precip_snow           => ' There is %s %s of snow on the ground.',
    temp_min_max_6_hours  => ' The high and low temperatures over the last 6 hours were %s and %s %s.',
    temp_max_6_hours      => ' The high temperature over the last 6 hours was %s %s.',
    temp_min_6_hours      => ' The low temperature over the last 6 hours was %s %s.',
    temp_min_max_24_hours => ' The high and low temperatures over the last 24 hours were %s and %s %s.',
    runway_vis            => ' The visibility for runway %s is %s meters (%s feet).',
    runway_vis_min_max    => ' The visibility for runway %s varies between %s meters (%s feet) and %s meters (%s feet).',
    light                 => ' light',
    moderate              => ' moderate',
    Heavy                 => ' heavy',
    nearby                => ' nearby',
    current_weather       => ' The weather condition was%s.',
    barometric_pressure   => ' and the pressure was %s %s',
    pretty_print_format   => 'This is the %s node weather report for the %s. At %s, the wind was %s. The temperature was %s %s%s%s.%s%s%s%s%s%s%s',
);

##
##
##

my $_weather_types_pat = join("|", keys(%_weather_types));

########################################################################
##
## DEFINE THE METAR METHODS
##
########################################################################

##
## Constructor.
##

sub new
{
    my $this = shift;
    my $class = ref($this) || $this;
    my $self = {};

    $self->{version}				= $VERSION;
    $self->{metar}				= undef;
    $self->{station}				= undef;
    $self->{type}				= undef;
    $self->{time}				= undef;
    $self->{mod}				= undef;
    $self->{wind_dir_deg}			= undef;
    $self->{wind_dir_txt}			= undef;
    $self->{wind_meters_per_second}		= undef;
    $self->{wind_knots}				= undef;
    $self->{wind_miles_per_hour}		= undef;
    $self->{wind_gust_meters_per_second}	= undef;
    $self->{wind_gust_knots}			= undef;
    $self->{wind_gust_miles_per_hour}		= undef;
    $self->{wind_var_beg}			= undef;
    $self->{wind_var_end}			= undef;
    $self->{visibility_miles}			= undef;
    $self->{visibility_km}			= undef;
    $self->{runway_nr}				= undef;
    $self->{runway_vis_ft}			= undef;
    $self->{runway_vis_meter}			= undef;
    $self->{runway_vis_ft_min}			= undef;
    $self->{runway_vis_meter_min}		= undef;
    $self->{runway_vis_ft_max}			= undef;
    $self->{runway_vis_meter_max}		= undef;
    $self->{weather}				= undef;
    $self->{cloud_layer1_condition}		= undef;
    $self->{cloud_layer1_coverage}		= undef;
    $self->{cloud_layer1_altitude_ft}		= undef;     
    $self->{cloud_layer1_altitude_m}		= undef;      
    $self->{cloud_layer2_condition}		= undef;
    $self->{cloud_layer2_coverage}		= undef;
    $self->{cloud_layer2_altitude_ft}		= undef;
    $self->{cloud_layer2_altitude_m}		= undef;
    $self->{cloud_layer3_condition}		= undef;
    $self->{cloud_layer3_coverage}		= undef;
    $self->{cloud_layer3_altitude_ft}		= undef;
    $self->{cloud_layer3_altitude_m}		= undef;
    $self->{dew_c}				= undef;
    $self->{dew_f}				= undef;
    $self->{altimeter_inhg}			= undef;
    $self->{altimeter_mmhg}			= undef;
    $self->{altimeter_hpa}			= undef;
    $self->{altimeter_atm}			= undef;
    $self->{temp_c}				= undef;
    $self->{temp_f}				= undef;
    $self->{temp_c_max6h}			= undef;
    $self->{temp_f_max6h}			= undef;
    $self->{temp_c_min6h}			= undef;
    $self->{temp_f_min6h}			= undef;
    $self->{temp_c_max24h}			= undef;
    $self->{temp_f_max24h}			= undef;
    $self->{temp_c_min24h}			= undef;
    $self->{temp_f_min24h}			= undef;
    $self->{precip_in}				= undef;
    $self->{precip_mm}				= undef;
    $self->{precip_6h_in}			= undef;
    $self->{precip_6h_mm}			= undef;
    $self->{precip_24h_in}			= undef;
    $self->{precip_24h_mm}			= undef;
    $self->{snow_in}				= undef;
    $self->{snow_mm}				= undef;
    $self->{rel_humidity}			= undef;
    $self->{humidex_c}				= undef;
    $self->{humidex_f}				= undef;
    $self->{windchill_f}			= undef;
    $self->{windchill_c}			= undef;
    $self->{heat_index_f}			= undef;
    $self->{heat_index_c}			= undef;
#
# Set the value of units to 'english' or 'metric'
#
    $self->{units}				= 'english';
    $self->{hrs24}				= 0;		# 0=12hr, 1=24hr
    $self->{debug}				= 0;		# 0=nodebug, 1=debug
 
    bless $self, $class;
    return $self;
}

########################################################################
## Read the METAR, process it, load up the METAR object and return the
## original METAR
##

sub fetch_metar {
    #
    # Fetches a new METER from STDIN and parses it
    # The new METAR is returned.
    #
    my $self = shift;
    my $metar = '';

    while (<STDIN>) {
       chomp;
       $metar = $_ if (/^([A-Z]{4,4}) /i);
    }
    $self->{metar} = $metar;
    _process($self);

    return $self->{metar};
}

########################################################################
## Autoload for access methods to stuff in %fields hash.
##

sub AUTOLOAD {
    my $self = shift;

    if (not ref $self) {
        cluck "bad AUTOLOAD for obj [$self]";
    }

    if ($AUTOLOAD =~ /.*::(.*)/) {
        my $key = $1;

        ## Check for the items...

        if (exists $self->{$key}) {
            return $self->{$key};
        } else {
            return undef;
        }
    } else {
        warn "strange AUTOLOAD problem!";
        return undef;
    }
}

########################################################################
## Get current version number.
##

sub version {
    my $self = shift;
    return $self->{version};
}


########################################################################
## Get site id
##

sub getstnid {
    my $self = shift;
    return $self->{site};
}

########################################################################
## Process the METAR.
##
## This function decodes a raw METAR.
##
sub _process
{
   my $self = shift;
   my $temp_visibility_miles = '';
   my $cloud_layers = 0;
   my $remarks = 0;

   ## Assume standard report by default

   $self->{type} = "METAR";
   print STDERR "DEBUG: metar $self->{metar}\n" if $self->{debug};

   foreach (split(/\s+/, $self->{metar})) {
 
      print STDERR "DEBUG: token [$_]..." if $self->{debug};

      if (! $remarks) {
         ##
         ## Type of Report
         ##

         if (/^(METAR|SPECI)$/i) {
            print STDERR "Type of Report.\n" if $self->{debug};
            $self->{type} = $1;
         }

         ##
         ## Station Identifier
         ##
         ## Changed to check all 4 letters. Used to required K as the
         ## first letter but this would break the program when used 
         ## to process data for non-USA weather.
         ##
         elsif ((! defined $self->{site}) && (/^([A-Z]{4,4})$/i)) {
            print STDERR "Station Identifier.\n" if $self->{debug};
            $self->{site} = $1;
         }

         ##
         ## Date and Time of Report
         ##
         elsif (/^(\d{2})(\d{2})(\d{2})Z$/i) {
            print STDERR "Date and Time of Report.\n" if $self->{debug};
            $self->{time} = timegm("00",$3,$2,$1,(gmtime)[4],(gmtime)[5]+1900);
         }

         ##
         ## Report Modifier
         ##
         elsif (/^(AUTO|COR|RTD|CC[A-Z]|RR[A-Z])$/i) {
            print STDERR "Report Modifier.\n" if $self->{debug};
            $self->{report_mod} = $1;
         }

         ##
         ## Wind Group
         ##
         elsif (/^(\d{2,3}|VRB)(\d{2,3})(G(\d{2,3}))?(KT|MPS|KMH)$/i) {
            print STDERR "Wind Group.\n" if $self->{debug};
            my $windunit = $5;
            #
            # now get the actual values
            #
            if ( $1 eq 'VRB' ) {
               $self->{wind_deg} = $_strings{wind_vrb_long};
               $self->{wind_dir_text} = $_strings{wind_vrb_long};
            } else {
               $self->{wind_deg} = $1;
               $self->{wind_dir_text} = $_wind_dir[int(($1/22.5)+0.5)];
            }
            ($self->{wind_meters_per_second},
             $self->{wind_knots},
             $self->{wind_miles_per_hour}) = conv_speed($2, $windunit);
            if (defined $3) {
               #
               # We have a report with information about gusting.
               #
               ($self->{wind_gust_meters_per_second},
                $self->{wind_gust_knots},
                $self->{wind_gust_miles_per_hour}) = conv_speed($4, $windunit);
            }
         }

         ##
         ## Wind Group - Variable Wind Direction
         ##
         elsif (/^(\d{3})V(\d{3})$/i) {
            print STDERR "Wind Group - Variable Wind Direction.\n" if $self->{debug};
            $self->{wind_var_beg} = $1;
            $self->{wind_var_end} = $2;
         }

         ##
         ## Visibility Group
         ## in meters (4 digits only)
         ##
         elsif (/^(\d{4})$/i) {
            if ( $1 == "9999" ) {
               #
               # Visibility Group Special Case
               #
               print STDERR "Visibility Group (>10 km).\n" if $self->{debug};
               $self->{visibility_miles} = '>6.2';
               $self->{visibility_km} = '>10';
            } else {
               #
               # Visibility Group Normal Case
               print STDERR "Visibility Group (meters).\n" if $self->{debug};
               $self->{visibility_km} = $1 / 1000;
               $self->{visibility_miles} = $self->{visibility_km} / 1.609344;
            }
         }

         ##
         ## Visibility Group - Whole Miles
         ## Save this token so that it can be used with Visibility Group below.
         ##
         elsif (/^M?\d$/i) {
            print STDERR "Visibility Group (Whole miles).\n" if $self->{debug};
            $temp_visibility_miles = $_;
         }

         ##
         ## Visibility Group - Whole or Fractional Miles
         ##
         ##           2     3      4
         elsif (/^((\d)(\/))?(\d*)SM$/i) {
            print STDERR "Visibility Group (Whole/Fractional miles).\n" if $self->{debug};

            my $vis_miles = ((defined $3) && ($3 eq '/')) ? $2 / $4 : $4;
            
            if (($temp_visibility_miles)[0] eq 'M') {
               $self->{visibility_miles} = '<'.$vis_miles;
               $self->{visibility_km} = '<'.($vis_miles * 1.609344);
            } else {
               $self->{visibility_miles} = $vis_miles;
               $self->{visibility_km} = $vis_miles * 1.609344;
            }
         }

         ##
         ## CAVOK: used when the visibility is greater than 10 km,
         ## the lowest cloud-base is at 5000 and there is no
         ## significant weather.
         ##
         elsif (/^CAVOK$/i) {
            print STDERR "is visibility information.\n" if $self->{debug};
            $self->{visibility_km}    = '>10';
            $self->{visibility_miles} = '>6.2';
            $self->{cloud_layer1_condition} = 'CAVOK';
         }

         ##
         ## Runway Visual Range Group
         ##
         elsif (/^R(\d{2}[RLC]?)\/([MP]?\d{4})V(P?\d{4})FT$/i) {
            print STDERR "is runway visual information.\n" if $self->{debug};
         }

         ##
         ## Present Weather Group
         ##
         elsif (/^(-|\+|VC)?($_weather_types_pat)+$/i) {
            print STDERR "Present Weather Group.\n" if $self->{debug};
            my $part = $_;
            if      ($part =~ /-/) {
               $self->{weather} .= $_strings{light};
               $part = substr($part, 1);
            } elsif ($part =~ /\+/) {
               $self->{weather} .= $_strings{heavy};
            } elsif (substr($part, 0, 2) =~ /VC/) {
               $self->{weather} .= $_strings{nearby};
               $part = substr($part, 2);
            } else {
               $self->{weather} .= $_strings{moderate};
            }

            while (my $bite=substr($part, 0, 2)) {
               $self->{weather} .= $_weather_types{$bite};
               $part = substr($part, 2);
            }
         }

         ##
         ## Sky Condition Group
         ## There can be up to three of these groups
         ##
         elsif (/^(CLR|SKC)$/i) {
            print STDERR "Sky Condition Group.\n" if $self->{debug};
            $cloud_layers++;
            
            $self->{'cloud_layer'.$cloud_layers.'_condition'} = $1;
            $self->{'cloud_layer'.$cloud_layers.'_coverage'}  = $1;
         }

         ##
         ## Sky Condition Group
         ## We found a(nother) cloud-layer group. There can be up to
         ## thrre of these groups.
         elsif (/^(VV|FEW|SCT|BKN|OVC)(\d{3})(CB|TCU)?$/i) {
           print STDERR "Sky Condition Group.\n" if $self->{debug};
           $cloud_layers++;
           if      ((defined $3) && ($3 eq 'CB')) {
              $self->{'cloud_layer'.$cloud_layers.'_condition'} = $_cloud_condition{$1} . $_strings{clouds_cb};
           } elsif ((defined $3) && ($3 eq 'TCU')) {
              $self->{'cloud_layer'.$cloud_layers.'_condition'} = $_cloud_condition{$1} . $_strings{clouds_tcu};
           } else {
              $self->{'cloud_layer'.$cloud_layers.'_condition'} = $_cloud_condition{$1} . (($1 eq 'OVC') ? '' : $_strings{clouds});
           }
           $self->{'cloud_layer'.$cloud_layers.'_coverage'} = $_cloud_coverage{$1};
           $self->{'cloud_layer'.$cloud_layers.'_altitude_ft'} = $2 * 100;
           $self->{'cloud_layer'.$cloud_layers.'_altitude_m'} = $2 * 30.48;
         }

         ##
         ## Temperature/Dew Point Group
         ##
         elsif (/^(M?\d{1,2})\/(M?\d{1,2})?$/i) {
            print STDERR "Temperature/Dew Point Group.\n" if $self->{debug};
            my $temp_c = $1;
            my $dew_c = $2 if (defined $2);
            $temp_c =~ s/^M/-/;
            $dew_c =~ s/^M/-/ if (defined $dew_c);
            ($self->{temp_c}, $self->{temp_f}) = conv_temp($temp_c);
            ($self->{dew_c}, $self->{dew_f}) = conv_temp($dew_c) if (defined $dew_c);
         }

         ##
         ## Altimeter
         ##
         elsif (/^A(\d{4})$/i) {
            print STDERR "Altimeter.\n" if $self->{debug};
            $self->{altimeter_inhg} = $1 / 100;
            $self->{altimeter_mmhg} = $1 * 0.254;
            $self->{altimeter_hpa}  = $1 * 0.33863881578947;
            $self->{altimeter_atm}  = $1 * 3.3421052631579e-4;
         }

         ##
         ## Remarks
         ##
         elsif (/^RMK$/i) {
            print STDERR "Begin Remarks Section.\n" if $self->{debug};
            $remarks = 1;
         }

         ##
         ## Unknown Token
         ##
         else {
            print STDERR "Unknown Token.\n" if $self->{debug};
         }

      } else {
         ##
         ## Now processing REMarks
         ##
         print STDERR "RMK..." if $self->{debug};
         ##
         ## Hourly Temperature and Dew Point
         ##
         if (/^T(\d{4})(\d{4})?$/i) {
            print STDERR "Hourly Temperature and Dew Point.\n" if $self->{debug};
            ($self->{temp_c}, $self->{temp_f}) = conv_coded_temp($1);
            ($self->{dew_c}, $self->{dew_f}) = conv_coded_temp($2) if (defined $2);
         }

         ##
         ## 6-Hourly Maximum Temperature
         ##
         elsif (/^1(\d{4})$/i) {
            print STDERR "6-Hourly Maximum Temperature.\n" if $self->{debug};
            ($self->{temp_max6h_c}, $self->{temp_max6h_f}) = conv_coded_temp($1);
         }

         ##
         ## 6-Hourly Minimum Temperature
         ##
         elsif (/^2(\d{4})$/i) {
            print STDERR "6-Hourly Minimum Temperature.\n" if $self->{debug};
            ($self->{temp_min6h_c}, $self->{temp_min6h_f}) = conv_coded_temp($1);
         }

         ##
         ## 24-Hour Maximum and Minimum Temperature
         ##
         elsif (/^4(\d{4})(\d{4})$/i) {
            print STDERR "24-Hour Maximum and Minimum Temperature.\n" if $self->{debug};
            ($self->{temp_max24h_c}, $self->{temp_max24h_f}) = conv_coded_temp($1);
            ($self->{temp_min24h_c}, $self->{temp_min24h_f}) = conv_coded_temp($2);
         }

         ##
         ## 3-Hourly Pressure Tendancy
         ##
         elsif (/^5(\d{4})$/i) {
            print STDERR "3-Hourly Pressure Tendancy.\n" if $self->{debug};
         }

         ##
         ## Hourly Precipitation Amount
         ## (stored as inches)
         ##
         elsif (/^P(\d{4})$/i) {
            print STDERR "Hourly Precipitation Amount.\n" if $self->{debug};
            $self->{precip_in} = $1 / 100;
            $self->{precip_mm} = $1 * 0.254;
         }

         ##
         ## 3- and 6-Hour Precipitation Amount
         ## (store as inches)
         ##
         elsif (/^6(\d{4})$/i) {
            print STDERR "3- and 6-Hour Precipitation Amount.\n" if $self->{debug};
            $self->{precip_6h_in} = $1 / 100;
            $self->{precip_6h_mm} = $1 * 0.254;
         }

         ##
         ## 24-Hour Precipitation Amount
         ## (store as inches)
         ##
         elsif (/^7(\d{4})$/i) {
            print STDERR "24-Hour Precipitation Amount.\n" if $self->{debug};
            $self->{precip_24h_in} = $1 / 100;
            $self->{precip_24h_mm} = $1 * 0.254;
         }

         ##
         ## Snow Depth on Ground
         ##
         elsif (/^4\/(\d{3})$/i) {
            print STDERR "Snow Depth on Ground.\n" if $self->{debug};
            $self->{snow_in} = $1;
            $self->{snow_mm} = $1 * 25.4;
         }

         ##
         ## Type of Automated Station
         ##
         elsif (/^AO(1|2)$/i) {
            print STDERR "Type of Automated Station.\n" if $self->{debug};
         }

         ##
         ## SEA Level Pressure
         ##
         elsif (/^SLP(\d{3}|NO)/i) {
            print STDERR "Sea Level Pressure.\n" if $self->{debug};
         }

         ##
         ## unknown.
         ##
         else {
            print STDERR "is unknown token.\n" if $self->{debug};
         }
      }
   }
   ##
   ## All done processing the data in the METAR
   ##
   ## Process Derived Values
   ##
    
   ## 
   ## Relative Humidity
   ##
   if (defined $self->{dew_c}) {
      $self->{rel_humidity} = (((112 - (0.1 * $self->{temp_c}) + $self->{dew_c}) / ((112 + (0.9 * $self->{temp_c}))))**8)*100.0;
   }

   ##
   ## Heat Index
   ##
   if ((defined $self->{rel_humidity}) && (defined $self->{temp_f}) && ($self->{temp_f} >= 80) && ($self->{rel_humidity} >= 40)) {
      my $T = $self->{temp_f};
      my $R = $self->{rel_humidity};
      $self->{heat_index_f} = -42.379 + (2.0491423 * $T) + (10.14333127 * $R) - (0.22475541 * $T * $R) - ((6.83783 * (10 ** -3)) * ($T ** 2)) - ((5.48171 * (10 ** -2)) * ($R ** 2)) + ((1.22874 * (10 ** -3)) * ($T ** 2) * $R) + ((8.5282 * (10 ** -4)) * $T * ($R ** 2)) - ((1.99 * (10 ** -6)) * ($T ** 2) * ($R ** 2));
      $self->{heat_index_c} = ($self->{heat_index_f} - 32) * 0.556;
   }

   ##
   ## Windchill
   ##
   if ((defined $self->{temp_f}) && ($self->{temp_f} <= 45) && ($self->{wind_miles_per_hour} >= 3)) {
      my $wf = $self->{wind_miles_per_hour} ** 0.16;
      $self->{windchill_f} = 35.74 + (0.6215 * $self->{temp_f}) - (35.75 * $wf) + (0.4275 * $self->{temp_f} * $wf);
      $self->{windchill_c} = ($self->{windchill_f} - 32) * 0.556;
   }

}

########################################################################
## Translate the precipitation values. If there there was precipitation
## but the amount was unmeasurable, then report "trace" for amount.
##
sub pretty_print_precip {
   my $self = shift;
   my $precip_mm = shift;
   my $precip_in = shift;

   print STDERR "DEBUG: Translate precipitation: \"$precip_mm\" mm, \"$precip_in\" in.\n" if $self->{debug};
   my $amount = (($precip_mm > 0) ? sprintf($_strings{mm_inches},
                                           (($self->{units} eq 'metric') ? $precip_mm : $precip_in),
                                           $_units{$self->{units}.'_depth'})
                                 : $_strings{precip_a_trace});

   return sprintf($_strings{precip_there_was}, $amount);
}

########################################################################
## Returns a string with a "human readable" text which is a translation 
## of the data contained in the METAR.
##
sub pretty_print {

   my $self = shift;
   my $location_str = shift;

   if (!$self->{metar}) {
       return sprintf($_strings{no_data}, $location_str);
   }

   ##
   ## Time
   ##
   my $minutes_old = int((time() - $self->{time}) / 60 + 0.5);
   my $min = (localtime($self->{time}))[1];
   my $hrs = (localtime($self->{time}))[2];
   if ((! $self->{hrs24}) && ($hrs > 12)) {
      $hrs -= 12;
   }
   my $lcltime_str = sprintf("%02s:%02s",$hrs,$min);

   ##
   ## Barometric Pressure
   ##
   my $pressure_str = ''; 
   if (defined $self->{altimeter_inhg}) {
      $pressure_str = sprintf($_strings{barometric_pressure},
                              (($self->{units} eq 'metric') ? int($self->{altimeter_hpa} * 100 + 0.5) / 100
                                                            : $self->{altimeter_inhg}),
                              $_units{$self->{units}.'_pressure'});
   }

   ##
   ## Cloud Layers
   ##
   my $sky_str;
   if ((defined $self->{cloud_layer1_condition}) && ($self->{cloud_layer1_condition} =~ /CAVOK/)) {
      $sky_str = sprintf($_strings{sky_cavok}, (($self->{units} eq 'metric') ? "1,525 meters" : "5,000 feet"));
   } elsif (defined $self->{cloud_layer1_altitude_ft}) {
      $sky_str = sprintf($_strings{sky_str_format1},
                         $self->{cloud_layer1_condition},
                         (($self->{units} eq 'metric') ? int($self->{cloud_layer1_altitude_m} + 0.5)
                                                       : $self->{cloud_layer1_altitude_ft}),
                         $_units{$self->{units}.'_height'});
      if (defined $self->{cloud_layer2_altitude_ft}) {
         if (defined $self->{cloud_layer3_altitude_ft}) {
            $sky_str .= sprintf($_strings{sky_str_format2},
                                $self->{cloud_layer2_condition},
                                (($self->{units} eq 'metric') ? int($self->{cloud_layer2_altitude_m} + 0.5)
                                                              : $self->{cloud_layer2_altitude_ft}),
                                $_units{$self->{units}.'_height'},
                                $self->{cloud_layer3_condition},
                                (($self->{units} eq 'metric') ? int($self->{cloud_layer3_altitude_m} + 0.5)
                                                              : $self->{cloud_layer3_altitude_ft}),
                                $_units{$self->{units}.'_height'});
         } else {
             $sky_str .= sprintf($_strings{sky_str_format3},
                                 $self->{cloud_layer2_condition},
                                 (($self->{units} eq 'metric') ? int($self->{cloud_layer2_altitude_m} + 0.5)
                                                               : $self->{cloud_layer2_altitude_ft}),
                                 $_units{$self->{units}.'_height'});
         }
      }
   } else {
      $sky_str = $_strings{sky_str_clear};
   }
   $sky_str .= '.';

   ##
   ## Visibility
   ##
   my $visibility_str = '';
   if (defined $self->{visibility_miles}) {
      if ($self->{visibility_miles} =~ /^>(.+)/) {
         $visibility_str = sprintf($_strings{visibility_format},
                               ($self->{units} eq 'metric') ? $self->{visibility_km}
                                                             : $self->{visibility_miles},
                               $_units{$self->{units}.'_distance'});
      } else {
         $visibility_str = sprintf($_strings{visibility_format},
                               int((($self->{units} eq 'metric') ? $self->{visibility_km}
                                                                 : $self->{visibility_miles}) + 0.5),
                               $_units{$self->{units}.'_distance'});
      }
   }

   ##
   ## Wind
   ##
   my $wind_str;
   if ((defined $self->{wind_meters_per_second}) && ($self->{wind_meters_per_second} > 0)) {
      $wind_str = sprintf($_strings{wind_str_format1},
                          int((($self->{units} eq 'metric') ? $self->{wind_meters_per_second}
                                                            : $self->{wind_miles_per_hour}) + 0.5),
                          $_units{$self->{units}.'_speed'});
      if ((defined $self->{wind_gust_meters_per_second}) && ($self->{wind_gust_meters_per_second} > 0)) {
         $wind_str .= sprintf($_strings{wind_str_format2},
                              int((($self->{units} eq 'metric') ? $self->{wind_gust_meters_per_second}
                                                                : $self->{wind_gust_miles_per_hour}) + 0.5),
                              $_units{$self->{units}.'_speed'});
      }
      $wind_str .= sprintf($_strings{wind_str_format3}, $self->{wind_dir_text});
   } else {
      $wind_str = $_strings{wind_str_calm};
   }

   ##
   ## Windchill
   ##
   my $feels_like_str = '';
   if      (defined $self->{windchill_c}) {
      $feels_like_str = sprintf($_strings{windchill},
                               int((($self->{units} eq 'metric') ? $self->{windchill_c}
                                                                 : $self->{windchill_f}) + 0.5),
                               $_units{$self->{units}.'_temperature'});
   } elsif (defined $self->{heat_index_f}) {
      $feels_like_str = sprintf($_strings{heat_index},
                               int((($self->{units} eq 'english') ? $self->{heat_index_f}
                                                                  : $self->{heat_index_c}) + 0.5),
                               $_units{$self->{units}.'_temperature'});
   }

   ##
   ## Relative Humidity
   ##
   my $rel_humidity_str = (defined $self->{rel_humidity}) ? sprintf($_strings{rel_humidity},
                                                                    int($self->{rel_humidity} + 0.5))
                                                          : '';

   ##
   ## Precipitation
   ##
   my $precip_str = '';
   if (defined $self->{precip_in}) {
      $precip_str = pretty_print_precip($self, int($self->{precip_mm} * 100 + 0.5) / 100, int($self->{precip_in} * 100 + 0.5) / 100) . 
                                      $_strings{precip_last_hour};
   }
   if (defined $self->{precip_6h_in}) {
      $precip_str .= pretty_print_precip($self, int($self->{precip_6h_mm} * 100 + 0.5) / 100, int($self->{precip_6h_in} * 100 + 0.5) / 100) . 
                                       $_strings{precip_last_6_hours};
   }
   if (defined $self->{precip_24h_in}) {
      $precip_str .= pretty_print_precip($self, int($self->{precip_24h_mm} * 100 + 0.5) / 100, int($self->{precip_24h_in} * 100 + 0.5) / 100) . 
                                       $_strings{precip_last_24_hours};
   }
   if (defined $self->{snow_in}) {
      $precip_str .= sprintf($_strings{precip_snow},
                           int((($self->{units} eq 'metric') ? $self->{snow_mm}
                                                             : $self->{snow_in}) + 0.5),
                           $_units{$self->{units}.'_depth'});
   }

   ##
   ## Min and Max Temperatures
   ##
   my $temp_str = '';
   if ((defined $self->{temp_max6h_c}) && (defined $self->{temp_min6h_c})) {
      $temp_str .= sprintf($_strings{temp_min_max_6_hours},
                           int((($self->{units} eq 'metric') ? $self->{temp_max6h_c}
                                                             : $self->{temp_max6h_f}) + 0.5),
                           int((($self->{units} eq 'metric') ? $self->{temp_min6h_c}
                                                             : $self->{temp_min6h_f}) + 0.5),
                           $_units{$self->{units}.'_temperature'});
   } else {
      if (defined $self->{temp_max6h_c}) {
         $temp_str .= sprintf($_strings{temp_max_6_hours},
                              int((($self->{units} eq 'metric') ? $self->{temp_max6h_c}
                                                                : $self->{temp_max6h_f}) + 0.5),
                              $_units{$self->{units}.'_temperature'});
      }
      if (defined $self->{temp_min6h_c}) {
         $temp_str .= sprintf($_strings{temp_min_6_hours},
                              int((($self->{units} eq 'metric') ? $self->{temp_max6h_c}
                                                                : $self->{temp_min6h_f}) + 0.5),
                              $_units{$self->{units}.'_temperature'});
      }
   }
   if (defined $self->{temp_max24h_c}) {
      $temp_str .= sprintf($_strings{temp_min_max_24_hours},
                           int((($self->{units} eq 'metric') ? $self->{temp_max24h_c}
                                                             : $self->{temp_max24h_f}) + 0.5),
                           int((($self->{units} eq 'metric') ? $self->{temp_min24h_c}
                                                             : $self->{temp_min24h_f}) + 0.5),
                           $_units{$self->{units}.'_temperature'});
   }

   ##
   ## Runway information
   ##
   my $runway_str = '';
   if (defined $self->{runway_vis_meter}) {
      $runway_str = sprintf($_strings{runway_vis},
                            $self->{runway_nr},
                            $self->{runway_vis_meter},
                            $self->{runway_vis_ft});
   }
   if (defined $self->{runway_vis_min_meter}) {
      $runway_str .= sprintf($_strings{runway_vis_min_max},
                             $self->{runway_nr},
                             $self->{runway_vis_min_meter},
                             $self->{runway_vis_min_ft},
                             $self->{runway_vis_max_meter},
                             $self->{runway_vis_max_ft});
   }

   ##
   ## Current Weather
   ##
   my $weather_str = '';
   if (defined $self->{weather}) {
      $weather_str = sprintf($_strings{current_weather}, $self->{weather});
   } 

   return sprintf($_strings{pretty_print_format},
                  $ENV{'CALL_SPACED'},
                  $location_str,
                  $lcltime_str,
                  $wind_str,
                  int((($self->{units} eq 'metric') ? $self->{temp_c} : $self->{temp_f}) + 0.5),
                  $_units{$self->{units}.'_temperature'},
                  $feels_like_str,
                  $pressure_str,
                  $rel_humidity_str,
                  $sky_str,
                  $visibility_str,
                  $runway_str,
                  $weather_str,
                  $precip_str,
                  $temp_str);
}

########################################################################
## Helper function to convert speed based on unit.
## Passed: speed and unit
## Returns: list of MPS,KTS,MPH
##
sub conv_speed {
   my $speed = shift;
   my $units = shift;

   my $kts = '';
   my $mps = '';
   my $mph = '';

   if    ($units =~ /KT/) {
      $kts = $speed;
      $mps = $speed * 0.51444;
      $mph = $speed * 1.1507695060844667;
   }
   elsif ($units =~ /MPS/) {
      $mps = $speed;
      $kts = $speed / 0.51444;
      $mph = $kts * 1.1507695060844667;
   }
   elsif ($units =~ /KMH/) {
      $mps = $speed * 1000 / 3600;
      $kts = $mps /  0.51444;
      $mph = $kts * 1.1507695060844667;
   }
   return $mps, $kts, $mph;
}

########################################################################
## Helper function to convert temperature.
## Passed: temp_Celcius
## Returns: list of temp_Celsius and temp_Farenheit
##
sub conv_temp {
   my $temp_c = shift;
   my $temp_f = ($temp_c * (9/5)) + 32;
   return $temp_c, $temp_f;
}

########################################################################
## Helper function to convert coded temperatures.
## Passed: coded temperature
## Returns: list of temp_Celcius and temp_Faenheit
##
sub conv_coded_temp {
   my $temp = shift;
   $temp =~ s/^1/-/;
   my$temp_c = $temp / 10;
   my $temp_f = $temp_c * (9/5) + 32;
   return $temp_c, $temp_f;
}

########################################################################
########################################################################
##
## MAIN PROGRAM
##
my $m = new METAR;

$m->fetch_metar();		# Read METAR from STDIN and process it

my $site = $m->getstnid();	# Retrieve the station ID (ICAO)

if ($site) {

   if (! open ICAO, "< /home/irlp/custom/nsd_cccc.txt") {
      print STDERR "Unable to open file nsd_cccc.txt\n";
      exit 0;
   }
   my ($icao,$blkno,$stnno,$location,$state,$country,$WMOREG,$stnlat,$stnlon,$ualat,$ualon,$stnelv,$uaelv,$RBSN);
   my $found_ICAO = 0;
   while (<ICAO>) {		# Find the Station Name
     chomp;
     ($icao,$blkno,$stnno,$location,$state,$country,$WMOREG,$stnlat,$stnlon,$ualat,$ualon,$stnelv,$uaelv,$RBSN) = split /\;/;
     if ($icao =~ /$site/) {
        $found_ICAO = 1;
        last;
     }
   }
   close ICAO;
   exit 0 if (! $found_ICAO);

   $location =~ s/.*?,\s//;

   print $m->pretty_print($location) . "\n"; # Display METAR in natural language
} else {
   print "Data for requested station is not available.\n";
}
exit 1;

