#!/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 () { 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 () { # 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;