You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1113 lines
38 KiB

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

Powered by TurnKey Linux.