From 0226bad9a8c58ab75622d755cca5bd98df6e63ac Mon Sep 17 00:00:00 2001 From: waynieack Date: Fri, 21 Oct 2016 21:36:58 -0500 Subject: [PATCH] Fixes for broken code due to NOAA site changes. Zone is no longer needed when get_weather is called. Bug #631 --- bin/get_weather | 12 +- lib/site/Geo/WeatherNOAA.pm | 1115 +++++++++++++++++++---------------- 2 files changed, 620 insertions(+), 507 deletions(-) diff --git a/bin/get_weather b/bin/get_weather index 1a753d514..83fda2bb1 100755 --- a/bin/get_weather +++ b/bin/get_weather @@ -59,7 +59,6 @@ Usage: -city xxx => xxx is the City you want. -state xxx => xxx is the State you want. - -zone xxx => xxx is the Zone (for forecast) you want. -data xxx => xxx is either conditions, forecast, or all. Default is all. @@ -76,10 +75,9 @@ eof my ( $conditions, $forecast, %data ); my %config_parms; -$parms{city} = 'Rochester' unless $parms{city}; -$parms{zone} = $parms{city} unless $parms{zone}; -$parms{state} = 'MN' unless $parms{state}; -$parms{data} = 'all' unless $parms{data}; +$parms{city} = 'Rochester' unless $parms{city}; +$parms{state} = 'MN' unless $parms{state}; +$parms{data} = 'all' unless $parms{data}; $data{conditions}++ if $parms{data} eq 'all' or $parms{data} eq 'conditions'; $data{forecast}++ if $parms{data} eq 'all' or $parms{data} eq 'forecast'; @@ -110,9 +108,9 @@ if ( $data{conditions} ) { } if ( $data{forecast} ) { - print "Getting the forecast for $parms{zone}, $parms{state}\n"; + print "Getting the forecast for $parms{city}, $parms{state}\n"; $forecast = - print_forecast( $parms{zone}, $parms{state}, undef, undef, undef, 1 ); + print_forecast( $parms{city}, $parms{state}, undef, undef, undef, 1 ); $forecast =~ s/Geo::WeatherNOAA.pm .+\n//; # Drop geo version #$forecast =~ s/\.\.\./\. /g; $forecast =~ s/(\()(EDT|EST|CDT|CST|MDT|MST|PDT|PST)(\) *)//g; diff --git a/lib/site/Geo/WeatherNOAA.pm b/lib/site/Geo/WeatherNOAA.pm index a5e6387c9..4b74570ab 100644 --- a/lib/site/Geo/WeatherNOAA.pm +++ b/lib/site/Geo/WeatherNOAA.pm @@ -1,6 +1,6 @@ - -# $Id$ - +# $Id: WeatherNOAA.pm,v 4.38 2006/12/10 21:58:11 msolomon Exp $ +# $Id: WeatherNOAA.pm,v 4.39 2016/08/17 21:58:11 rsteeves Exp $ +# $Id: WeatherNOAA.pm,v 4.40 2016/09/28 21:58:11 wgatlin Exp $ package Geo::WeatherNOAA; @@ -14,28 +14,26 @@ use Text::Wrap; require Exporter; @ISA = qw(Exporter); + # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. @EXPORT = qw( - make_noaa_table + make_noaa_table - print_forecast - print_current + print_forecast + print_current - get_city_zone - process_city_zone + get_city_zone + process_city_zone - get_city_hourly - process_city_hourly + get_city_hourly + process_city_hourly ); -my $revision = '$Revision$'; - $revision =~ m/: (\d+)/; - $revision = $1; - $VERSION = $revision; - -my $URL_BASE = 'http://www.weather.gov/view/prodsByState.php';#'http://iwin.nws.noaa.gov/iwin/'; +$VERSION = do { my @r = ( q$Revision: 4.40 $ =~ /\d+/g ); sprintf "%d." . "%02d" x $#r, @r }; +my $URL_BASE = 'http://forecast.weather.gov/product.php?site='; +my $ZONE_SEARCH_URL = 'http://forecast.weather.gov/zipcity.php'; use vars '$proxy_from_env'; $proxy_from_env = 0; @@ -43,27 +41,26 @@ $proxy_from_env = 0; # Preloaded methods go here. sub print_forecast { - my ($city, $state, $filename, $fileopt, $UA) = @_; - my $in = get_city_zone($city,$state,$filename,$fileopt,$UA); + my ( $city, $state, $filename, $fileopt, $UA ) = @_; + my $in = get_city_zone( $city, $state, $filename, $fileopt, $UA ); - my $out; + my $out; - $out = "Geo::WeatherNOAA.pm v.$Geo::WeatherNOAA::VERSION\n"; + $out = "Geo::WeatherNOAA.pm v.$Geo::WeatherNOAA::VERSION\n"; - my ($date,$warnings,$forecast) = - process_city_zone($city,$state,$filename,$fileopt); + my ( $date, $warnings, $forecast ) = + process_city_zone( $city, $state, $filename, $fileopt ); - $out .= "As of $date:\n"; - foreach my $warning (@$warnings) { - $out .= wrap('WARNING: ',' ',"$warning\n"); - } - foreach my $key (keys %$forecast) { - $out .= wrap('',' ',"$key: $forecast->{$key}\n"); - } - return $out + $out .= "As of $date:\n"; + foreach my $warning (@$warnings) { + $out .= wrap( 'WARNING: ', ' ', "$warning\n" ); + } + foreach my $key ( keys %$forecast ) { + $out .= wrap( '', ' ', "$key: $forecast->{$key}\n" ); + } + return $out; } - ######################################################################### ######################################################################### # @@ -72,241 +69,295 @@ sub print_forecast { ######################################################################### ######################################################################### sub process_city_zone { - my ($city, $state, $filename, $fileopt, $UA) = @_; - my $in = get_city_zone($city,$state,$filename,$fileopt,$UA); - - # Return error if problem getting URL - if ($in =~ /Error/) { - my %error; - my @null; - $error{'Error'} = 'Error'; - $error{'Network Error'} = $in; - return ('',\@null,\%error); - } - - # Split coverage, date, and forecast - # - my ($coverage, $date, $forecast) = ($in =~ /(^.*?)\012 # Coverage - (\d.*?)\012 # Date - (.*)/sx); # Entire Forecast - - # Format Coverage - # - $coverage =~ s/corrected//gi; # Remove stat word - $coverage =~ s/(\/|-|\.\.\.)/, /g; # Turn weird punct to commas - $coverage =~ s/,\s*$//; # Remove last comma - $coverage = ucfirst_words($coverage); # Make caps correct - - # Format date (easy) - # - $date = format_date($date); - - # Vars for forecast - # - my %forecast; - tie %forecast, "Tie::IxHash"; - my @warnings; - - # Iterate through forecast and assign warnings to list or pairs to hash - # - my $forecast_item; # Used as place holder for line breaks of $value - my $warnings_done = 0; # Flag for warnings (Always at top of forcast) - - foreach my $line (split "\012",$forecast) { - # Be-gone if we've got temp data (will include parse for that later) - last if $line =~ /^\.$in|; + my $in = shift; + my $size = shift || 2; + my $font_face = $main::font_face || 'FACE="Helvetica, Lucida, Ariel"'; + return qq|$in|; } sub make_noaa_table { - my ($city, $state, $filename, $fileopt, $UA, $max_items) = @_; - - $fileopt ||= 'get'; - $max_items && $max_items--; - $max_items ||= 4; - - my $med_bg = $main::med_bg || '#ddddff'; - my $light_bg = $main::light_bg || '#eeeeff'; - my $font_face = $main::font_face || 'FACE="Helvetica, Lucida, Ariel"'; - - my $locfilename; - $locfilename = $filename . "_hourly"; - my $current = process_city_hourly( $city,$state,$locfilename,$fileopt,$UA ); - - $locfilename = $filename . "_zone"; - my ($date,$warnings,$forecast,$coverage) = process_city_zone( $city,$state,$locfilename,$fileopt,$UA); - my $cols = (keys %$forecast); - $cols = $max_items if $cols > $max_items; - my $out; - $out .= qq|\n|; - $out .= qq|\n|; - $out .= qq|\n"; - $out .= qq|\n"; - - # Add one to make cols real width of table - # - $cols++; - - # Add warnings, if needed - # - if (@$warnings) { - $out .= qq|\n|; - foreach my $warning (@$warnings) { - $out .= qq|\n|; - $out .= qq|\t\n|; - } - } - - # Iterate over the first $max_items items in forecast - # - my $bottom; # add this after the iteration; - $out .= qq|\n|; - $bottom .= qq|\n|; - foreach my $key ( (keys %$forecast)[0..($cols - 1)] ) { - #print STDERR "DEBUG: $key\n"; - $out .= "\t\n"; - $bottom .= "\t\n"; - } - $out .= "\n" . $bottom . "\n"; - - # Add coverage area - $out .= qq|\n|; - $out .= qq| \n|; - $out .= qq| \n|; - $out .= qq|\n|; - - # Add credits - # - my $wx_cred = 'NOAA forecast made ' . - "$date by " . - "" . - "Geo::WeatherNOAA V.$Geo::WeatherNOAA::VERSION"; - $out .= qq|\n|; - $out .= qq|\n"; - $out .= qq|
\n|; - $out .= font('Current') . "\n|; - $out .= font($current) . "\n
|; - $out .= qq|\n|; - $out .= qq|\t$warning\n
" . font($key) . "" . font($forecast->{$key}) . "
| . font('Area') . qq|| . font($coverage,1) . qq|
| . font($wx_cred) . "
\n|; + my ( $city, $state, $filename, $fileopt, $UA, $max_items ) = @_; + + $fileopt ||= 'get'; + $max_items && $max_items--; + $max_items ||= 4; + + my $med_bg = $main::med_bg || '#ddddff'; + my $light_bg = $main::light_bg || '#eeeeff'; + my $font_face = $main::font_face || 'FACE="Helvetica, Lucida, Ariel"'; + + my $locfilename; + $locfilename = $filename . "_hourly"; + my $current = + process_city_hourly( $city, $state, $locfilename, $fileopt, $UA ); + + $locfilename = $filename . "_zone"; + my ( $date, $warnings, $forecast, $coverage ) = + process_city_zone( $city, $state, $locfilename, $fileopt, $UA ); + my $cols = ( keys %$forecast ); + $cols = $max_items if $cols > $max_items; + my $out; + $out .= qq|\n|; + $out .= qq|\n|; + $out .= qq|\n"; + $out .= qq|\n"; + + # Add one to make cols real width of table + # + $cols++; + # Add warnings, if needed + # + if (@$warnings) { + $out .= qq|\n|; + foreach my $warning (@$warnings) { + $out .= qq|\n|; + $out .= qq|\t\n|; + } + } + # Iterate over the first $max_items items in forecast + # + my $bottom; # add this after the iteration; + $out .= qq|\n|; + $bottom .= qq|\n|; + foreach my $key ( ( keys %$forecast )[ 0 .. ( $cols - 1 ) ] ) { + + #print STDERR "DEBUG: $key\n"; + $out .= "\t\n"; + $bottom .= "\t\n"; + } + $out .= "\n" . $bottom . "\n"; + + # Add coverage area + $out .= qq|\n|; + $out .= qq| \n|; + $out .= qq| \n|; + $out .= qq|\n|; + + # Add credits + # + my $wx_cred = + 'NOAA forecast made ' + . "$date by " + . "" + . "Geo::WeatherNOAA V.$Geo::WeatherNOAA::VERSION"; + $out .= qq|\n|; + $out .= qq|\n"; + $out .= qq|
\n|; + $out .= font('Current') . "\n|; + $out .= font($current) . "\n
|; + $out .= qq|\n|; + $out .= qq|\t$warning\n
" . font($key) . "" . font( $forecast->{$key} ) . "
| . font('Area') . qq|| . font( $coverage, 1 ) . qq|
| . font($wx_cred) . "
\n|; } @@ -314,77 +365,110 @@ sub make_noaa_table { ############################################################################## ## ## Misc funcs -## +## ############################################################################## ############################################################################## +sub get_zone { + my ( $URL, $CityState, $UA ) = @_; + + $URL or die "No URL to get!"; + + # Create the useragent and get the data + # + if ( !$UA ) { + $UA = new LWP::UserAgent; + if ( $ENV{'HTTP_PROXY'} or $ENV{'http_proxy'} ) { + $UA->env_proxy; + } + } + + $UA->agent("WeatherNOAA/$VERSION"); + + my $ua = LWP::UserAgent->new(); + my $response = + $ua->post( $URL, { 'inputstring' => $CityState, 'siteid' => 'chr' } ); + my $location = $response->header('Location'); + + if ( $location =~ /&site=(...)&/ ) { + return $1; + } + else { + return; + } +} + sub get_url { - my ($URL, $UA) = @_; + my ( $URL, $UA ) = @_; - $URL or die "No URL to get!"; + $URL or die "No URL to get!"; # Create the useragent and get the data # - if (! $UA) { - $UA = new LWP::UserAgent; - $UA->env_proxy if $proxy_from_env; + if ( !$UA ) { + $UA = new LWP::UserAgent; + if ( $ENV{'HTTP_PROXY'} or $ENV{'http_proxy'} ) { + $UA->env_proxy; + } } $UA->agent("WeatherNOAA/$VERSION"); - + # Create a request my $req = new HTTP::Request GET => $URL; my $res = $UA->request($req); - if ($res->is_success) { - return $res->content; + if ( $res->is_success ) { + return $res->content; } else { - return; + return; } -} # getURL() +} # getURL() sub get_data { - my ($URL,$filename,$fileopt,$UA) = @_; - - $fileopt ||= 'get'; - - my $data; # Data - - if ( ($fileopt eq 'get') || ($fileopt eq 'save') ) { - print STDERR "Retrieving $URL\n" if $main::opt_v; - $data = get_url($URL,$UA) || - return "Error getting data from $URL"; - if ( $fileopt eq 'save' ) { - print STDERR "Writing $URL to $filename\n" if $main::opt_v; - open(OUT,">$filename") or die "Cannot create $filename"; - print OUT $data; - close OUT; - $fileopt = 'usefile'; - } - } - if ( $fileopt eq 'usefile' ) { - print STDERR "Reading data from $filename\n" if $main::opt_v; - open(FILE,$filename) or die "Cannot read $filename"; - while () { $data .= $_; } - } - return $data; -} # get_fh + my ( $URL, $filename, $fileopt, $UA ) = @_; + + $fileopt ||= 'get'; + + my $data; # Data + + if ( ( $fileopt eq 'get' ) || ( $fileopt eq 'save' ) ) { + print STDERR "Retrieving $URL\n" if $main::opt_v; + $data = get_url( $URL, $UA ) + || return "Error getting data from $URL"; + if ( $fileopt eq 'save' ) { + print STDERR "Writing $URL to $filename\n" if $main::opt_v; + open( OUT, ">$filename" ) or die "Cannot create $filename"; + print OUT $data; + close OUT; + $fileopt = 'usefile'; + } + } + if ( $fileopt eq 'usefile' ) { + print STDERR "Reading data from $filename\n" if $main::opt_v; + open( FILE, $filename ) or die "Cannot read $filename"; + while () { $data .= $_; } + } + return $data; +} # get_fh sub format_date { - my $in = shift; - $in =~ s/^(\d+)(\d\d)\s(AM|PM)\s(\w+)\s(\w+)\s(\w+)\s0*(\d+)/$1:$2\L$3\E ($4) \u\L$5\E\E \u\L$6 $7,/; - $in =~ tr/\015//d; # \r - return $in; + my $in = shift; + $in =~ + s/^(\d+)(\d\d)\s(AM|PM)\s(\w+)\s(\w+)\s(\w+)\s0*(\d+)/$1:$2\L$3\E ($4) \u\L$5\E\E \u\L$6 $7,/; + $in =~ tr/\015//d; # \r + return $in; } + sub sent_caps { - my $in = shift; - $in = ucfirst(lc($in)); - $in =~ s/(\.\W+)(\w)/$1\U$2/g; # Proper sentance caps - return $in; + my $in = shift; + $in = ucfirst( lc($in) ); + $in =~ s/(\.\W+)(\w)/$1\U$2/g; # Proper sentance caps + return $in; } sub ucfirst_words { - my ($in) = @_; - return join " ", map ucfirst(lc($_)),(split /\s+/, $in); + my ($in) = @_; + return join " ", map ucfirst( lc($_) ), ( split /\s+/, $in ); } ######################################################################### @@ -396,183 +480,210 @@ sub ucfirst_words { ######################################################################### sub get_city_hourly { - my ($city,$state,$filename,$fileopt,$UA) = @_; - - # City and state in all caps please - # - $city = uc $city; - $state = uc $state; - - # work var - my ($fields,$line,$date,$time); - - # Get data - # -# my $URL = $URL_BASE . lc $state . '/hourly.html'; - my $URL = $URL_BASE . '?state=' . lc $state . '&prodtype=hourly'; - -#print STDERR "Getting data\n"; - my $data = get_data($URL,$filename,$fileopt,$UA); - #print STDERR "Got data\n"; - - # required for new data format: - $data =~ s/\012\s/\012/g; - - # Return error if there's an error - if ($data =~ /Error/) { - my %retHash; - $retHash{ERROR} = $data; - return \%retHash; - } - - $data =~ s/\015//g; # \r - - # Get line for our city from Data - # - foreach (split /\012/, $data) { - chomp; - $date = $_ if /^\s*(\d+)(\d\d)\s+(AM|PM)\s+(\w+)/; - $time = "$1:$2 $3" if (($1) && ($2) && ($3)); - $fields = $_ if /^CITY/; - $line = $_ if /^$city\s/; - - # Newest data seems to be at the top of the file - last if $line; - } - $date = format_date($date); - - # Set pack strings - # - my $fields_pack_str; - my $values_pack_str; - if ( ($fields =~ /TMP/) and ($fields =~ /\sDP\s/) ) { - #print STDERR "NEW FORMAT!\n"; - $fields_pack_str = - '@0 A15 @15 A9 @25 A3 @29 A2 @33 A2 @36 A8 @47 A5 @54 A7'; - $values_pack_str = - '@0 A15 @15 A8 @24 A4 @28 A4 @32 A3 @36 A8 @46 A7 @53 A8'; - } - else { - #print STDERR "OLD FORMAT!\n"; - $fields_pack_str = - '@0 A15 @15 A9 @24 A5 @29 A5 @35 A4 @39 A8 @47 A8 @55 A8'; - $values_pack_str = - '@0 A15 @15 A9 @24 A5 @29 A5 @34 A4 @39 A8 @47 A8 @55 A8'; - } - - # unpack gives error of the string is smaller than the unpack string - $line .= ' ' x (64 - length($line)) if length($line) < 64; - - return { } unless ( ($line) && ($fields) ); # Return ref to empty hash - - my @fields; - push @fields, 'DATE', 'TIME', unpack $fields_pack_str, $fields if $fields; - #'@0 A15 @15 A9 @24 A5 @29 A5 @35 A4 @39 A8 @47 A8 @55 A8', $fields if $fields; - my @values; - push @values, $date, $time, unpack $values_pack_str, $line; - #print STDERR "$line\n"; - #'@0 A15 @15 A9 @24 A5 @29 A5 @34 A4 @39 A8 @47 A8 @55 A8', $line; - - - - return { } if $values[3] eq 'NOT AVBL'; # Return ref to empty hash - - my %retValue; - foreach my $i (0..$#fields) { - # Convert odd fieldnames to standard - $fields[$i] = 'DEWPT' if $fields[$i] eq 'DP'; - $fields[$i] = 'TEMP' if $fields[$i] eq 'TMP'; - - # Assign value - $retValue{$fields[$i]} = $values[$i]; - } - - return \%retValue; - -} # get_city_hourly() + my ( $city, $state, $filename, $fileopt, $UA ) = @_; + + # City and state in all caps please + # + $city = uc $city; + $state = uc $state; + + # work var + my ( $fields, $line, $date, $time ); + + # Get data + # + my $zone = &get_zone( $ZONE_SEARCH_URL, "$city, $state" ); + my $URL = + $URL_BASE + . $zone + . '&issuedby=' + . $zone + . '&product=RWR&format=txt&version=1&glossary=0'; + + #print STDERR "Getting data from $URL\n"; + my $data = get_data( $URL, $filename, $fileopt, $UA ); + my $datalength = length($data); + + #print STDERR "Got data ($datalength)\n"; + + # Return error if there's an error + if ( $data =~ /Error/ ) { + my %retHash; + $retHash{ERROR} = $data; + return \%retHash; + } + + $data =~ s/\015//g; # \r + + #print STDERR "LOOKING FOR: " . $city . "\n"; + + # Get line for our city from Data + # + foreach ( split /\012/, $data ) { + chomp; + s/^\s*//; + $date = $_ if /^\s*(\d+)(\d\d)\s+(AM|PM)\s+(\w+)/; + $time = "$1:$2 $3" if ( ($1) && ($2) && ($3) ); + $fields = $_ if /^CITY/; + $line = $_ if /^$city/; + + #print STDERR "LINE: $line\n" if $line; + + # Newest data seems to be at the top of the file + last if $line; + } + $date = format_date($date); + + # Set pack strings + # + my $fields_pack_str; + my $values_pack_str; + if ( ( $fields =~ /TMP/ ) and ( $fields =~ /\sDP\s/ ) ) { + + #print STDERR "NEW FORMAT!\n"; + $fields_pack_str = + '@0 A15 @15 A9 @25 A3 @29 A2 @33 A2 @36 A8 @47 A5 @54 A7'; + $values_pack_str = + '@0 A15 @15 A8 @24 A4 @28 A4 @32 A3 @36 A8 @46 A7 @53 A8'; + } + else { + #print STDERR "OLD FORMAT!\n"; + $fields_pack_str = + '@0 A15 @15 A9 @24 A5 @29 A5 @35 A4 @39 A8 @47 A8 @55 A8'; + $values_pack_str = + '@0 A15 @15 A9 @24 A5 @29 A5 @34 A4 @39 A8 @47 A8 @55 A8'; + } + + # unpack gives error of the string is smaller than the unpack string + $line .= ' ' x ( 64 - length($line) ) if length($line) < 64; + + return {} unless ( ($line) && ($fields) ); # Return ref to empty hash + + my @fields; + push @fields, 'DATE', 'TIME', unpack $fields_pack_str, $fields if $fields; + + #'@0 A15 @15 A9 @24 A5 @29 A5 @35 A4 @39 A8 @47 A8 @55 A8', $fields if $fields; + my @values; + push @values, $date, $time, unpack $values_pack_str, $line; + + #print STDERR "$line\n"; + #'@0 A15 @15 A9 @24 A5 @29 A5 @34 A4 @39 A8 @47 A8 @55 A8', $line; + + return {} if $values[3] eq 'NOT AVBL'; # Return ref to empty hash + + my %retValue; + foreach my $i ( 0 .. $#fields ) { + + # Convert odd fieldnames to standard + $fields[$i] = 'DEWPT' if $fields[$i] eq 'DP'; + $fields[$i] = 'TEMP' if $fields[$i] eq 'TMP'; + + # Assign value + $retValue{ $fields[$i] } = $values[$i]; + } + + return \%retValue; + +} # get_city_hourly() sub print_current { - my ($city,$state,$filename,$fileopt,$UA) = @_; - my $in = process_city_hourly($city, $state, $filename, $fileopt,$UA); - return wrap('',' ',$in) + my ( $city, $state, $filename, $fileopt, $UA ) = @_; + my $in = process_city_hourly( $city, $state, $filename, $fileopt, $UA ); + return wrap( '', ' ', $in ); } - sub process_city_hourly { - my ($city,$state,$filename,$fileopt,$UA) = @_; - my $in = get_city_hourly($city, $state, $filename, $fileopt,$UA); - - $state = uc($state); - - return $in->{ERROR} if $in->{ERROR}; - $in->{CITY} or return "No data available"; - $in->{CITY} = ucfirst_words($in->{CITY}); - - my %sky = ( - 'SUNNY' => 'sunny skies', - 'MOSUNNY' => 'mostly sunny skies', - 'PTSUNNY' => 'partly sunny skies', - 'CLEAR' => 'clear weather', - 'DRIZZLE' => 'a drizzle', - 'CLOUDY' => 'cloudy skies', - 'MOCLDY' => 'mostly cloudy skies', - 'PTCLDY' => 'partly cloudy skies', - 'LGT RAIN' => 'light rain', - 'FRZ DRZL' => 'freezing drizzle', - 'FLURRIES' => 'flurries', - 'LGT SNOW' => 'light snow', - 'SNOW' => 'snow', - 'N/A' => 'N/A', - 'NOT AVBL' => '*not available*', - 'FAIR' => 'fair weather'); - - # Format the wind direction and speed - # - my %compass = qw/N north S south E east W west/; -# my $direction = join '',map $compass{$_},split(/(\w)\d/g, $in->{WIND}); - my $direction = join '',map $compass{$_},split(/(\w)\d/, $in->{WIND}); # Drop /g to avoid perl 5.8 warning - my ($speed) = ($in->{WIND} =~ /(\d+)/); - my ($gusts) = ($in->{WIND} =~ /G(\d+)/); - - if ($in->{WIND} eq 'CALM') { - $in->{WIND} = 'calm'; - } - else { - $in->{WIND} = "$direction at ${speed} mph"; - $in->{WIND} .= ", gusts up to ${gusts} mph" if $gusts; - } - - # Format relative humidity and ibarometric pressure - # - my $rh_pres; - if ($in->{RH}) { - $rh_pres = " The relative humidity was $in->{RH}\%"; - } - if ($in->{PRES}) { - my %rise_fall = qw/R rising S steady F falling/; -# bbw Avoide a perl 5.8 warning -# my $direction = join '',map $rise_fall{$_},split(/\d(\w)/g, $in->{PRES}); - my $direction = join '',map $rise_fall{$_},split(/\d(\w)/, $in->{PRES}); - $in->{PRES} =~ tr/RSF//d; - if ($rh_pres) { - $rh_pres .= ", and b"; - } - else { - $rh_pres .= " B"; - } - $rh_pres .= "arometric pressure was $direction from $in->{PRES} in"; - } - $rh_pres .= '.' if $rh_pres; + my ( $city, $state, $filename, $fileopt, $UA ) = @_; + my $in = get_city_hourly( $city, $state, $filename, $fileopt, $UA ); + + $state = uc($state); + + return $in->{ERROR} if $in->{ERROR}; + $in->{CITY} or return "No data available"; + $in->{CITY} = ucfirst_words( $in->{CITY} ); + + my %sky = ( + 'SUNNY' => 'sunny skies', + 'MOSUNNY' => 'mostly sunny skies', + 'PTSUNNY' => 'partly sunny skies', + 'CLEAR' => 'clear weather', + 'DRIZZLE' => 'a drizzle', + 'CLOUDY' => 'cloudy skies', + 'MOCLDY' => 'mostly cloudy skies', + 'PTCLDY' => 'partly cloudy skies', + 'LGT RAIN' => 'light rain', + 'FRZ DRZL' => 'freezing drizzle', + 'FLURRIES' => 'flurries', + 'LGT SNOW' => 'light snow', + 'SNOW' => 'snow', + 'N/A' => 'N/A', + 'NOT AVBL' => '*not available*', + 'FAIR' => 'fair weather' + ); + + # Format the wind direction and speed + # + my %compass = qw/N north S south E east W west/; + + # my $direction = join '',map $compass{$_},split(/(\w)\d/g, $in->{WIND}); + my $direction; + { + $direction = $in->{WIND}; + $direction =~ s/(.*?)G.*/$1/; # Remove gusts + $direction =~ s/\d//g; # Remove digits + if ($direction) { + $direction = $compass{$direction}; + } + } + my ($speed) = ( $in->{WIND} =~ /(\d+)/ ); + my ($gusts) = ( $in->{WIND} =~ /G(\d+)/ ); - # Format output sentence - # - my $out; - $out = "At $in->{TIME}, $in->{CITY}, $state conditions were "; - $out .= $sky{$in->{'SKY/WX'}} . " "; - $out .= "at $in->{TEMP}°F, wind was $in->{WIND}. $rh_pres\n"; - return $out; + if ( $in->{WIND} eq 'CALM' ) { + $in->{WIND} = 'calm'; + } + else { + $in->{WIND} = "$direction at ${speed} mph"; + $in->{WIND} .= ", gusts up to ${gusts} mph" if $gusts; + } -} # process_city_hourly() + # Format relative humidity and ibarometric pressure + # + my $rh_pres; + if ( $in->{RH} ) { + $rh_pres = " The relative humidity was $in->{RH}\%"; + } + if ( $in->{PRES} ) { + my %rise_fall = qw/R rising S steady F falling/; + + # my $direction = join '',map $rise_fall{$_},split(/\d(\w)/g, $in->{PRES}); + my $direction; + { + $direction = $in->{PRES}; + $direction = ( $direction =~ /.*(\w)$/ )[0]; + if ($direction) { + $direction = $rise_fall{$direction}; + } + } + $in->{PRES} =~ tr/RSF//d; + if ($rh_pres) { + $rh_pres .= ", and b"; + } + else { + $rh_pres .= " B"; + } + $rh_pres .= "arometric pressure was $direction from $in->{PRES} in"; + } + $rh_pres .= '.' if $rh_pres; + + # Format output sentence + # + my $out; + $out = "At $in->{TIME}, $in->{CITY}, $state conditions were "; + $out .= $sky{ $in->{'SKY/WX'} } . " "; + $out .= "at $in->{TEMP}°F, wind was $in->{WIND}. $rh_pres\n"; + return $out; + +} # process_city_hourly() # Autoload methods go after =cut, and are processed by the autosplit program. @@ -587,13 +698,13 @@ Geo::WeatherNOAA - Perl extension for interpreting the NOAA weather data =head1 SYNOPSIS use Geo::WeatherNOAA; - ($date,$warnings,$forecast,$coverage) = + ($date,$warnings,$forecast,$coverage) = process_city_zone('newport','ri','','get'); foreach $key (keys %$forecast) { - print "$key: $forecast->{$key}\n"; + print "$key: $forecast->{$key}\n"; } - + print process_city_hourly('newport news', 'va', '', 'get'); or @@ -653,65 +764,68 @@ if FILEOPT is "save" FILEOPT can be one of the following - - save - will get and save the data to FILENAME - - get - will retrieve new data (not store it) - - usefile - will not retrieve data from URL, - use FILENAME for data + - save + will get and save the data to FILENAME + - get + will retrieve new data (not store it) + - usefile + will not retrieve data from URL, + use FILENAME for data The fifth argument is for a user created LWP::UserAgent(3) which can -be configured to work with firewalls. See the LWP::UserAgent(3) manpage -for specific instructions. A basic example is like this: +be configured to work with firewalls. See the LWP::UserAgent(3) manpage +for specific instructions. A basic example is like this: my $ua = new LWP::UserAgent; $ua->proxy(['http', 'ftp'], 'http://proxy.my.net:8080/'); -If you merely wish to set your proxy data from environment -variables (as in $ua-env_proxy>), simply set - - $Geo::WeatherNOAA::proxy_from_env = 1; - +NOTE: You may also set the environment variable http_proxy +and the auto-generated LWP::UserAgent will use LWP::UserAgent::env_proxy(). +See LWP::UserAgent for more details. =item * process_city_zone(CITY,STATE,FILENAME,FILEOPT,LWP_UserAgent) Call CITY, STATE, FILENAME (explained above), FILEOPT(explained above), and UserAgent (Explained above). +Note that in August 2016 the NOAA site stopped using STATE as the defining +field, instead using 3-digit regional codes, available at: +http://forecast.weather.gov/product_sites.php?site=CRH&product=ZFP +All of the $state values should be the 3-digit code. + The return is a three element list containing a) a string of the date/time of the forecast, b) a reference to the list of warnings (if any), and c) a reference to the hash of forecast. I recommend calling it like this: - ($date, $warnings, $forecast, $coverage) = + ($date, $warnings, $forecast, $coverage) = process_city_zone('newport news','va', - '/tmp/va_zone.html', 'save'); + '/tmp/va_zone.html', 'save'); Explanation of this call, it returns: - $date - - Scalar of the date of the forecast + $date + - Scalar of the date of the forecast - $warnings - - Reference to the warnings list - - EXAMPLE: - foreach (@$warnings) { print; } - - $forecast - - Reference to the forecast KEY, VALUE pairs - - EXAMPLE: - foreach $key (keys %$forecast) { - print "$key: $forecast->{$key}\n"; - } + $warnings + - Reference to the warnings list + - EXAMPLE: + foreach (@$warnings) { print; } - $coverage - - Scalar of the coverage area of the forecast + $forecast + - Reference to the forecast KEY, VALUE pairs + - EXAMPLE: + foreach $key (keys %$forecast) { + print "$key: $forecast->{$key}\n"; + } + + $coverage + - Scalar of the coverage area of the forecast =item * get_city_zone(CITY,STATE,FILENAME,FILEOPT,LWP_UserAgent) This sub is to get the block of data from the data source, which is -chosen with the FILEOPTswitch. +chosen with the FILEOPTswitch. =item * get_city_hourly(CITY,STATE,FILENAME,FILEOPT,LWP_UserAgent) @@ -722,7 +836,7 @@ and UserAgent. This function returns a reference to a hash containing the data. It -Same FILEOPTand LWP_UserAgent from above, and process the +Same FILEOPTand LWP_UserAgent from above, and process the current weather data into an english sentence. =back @@ -740,3 +854,4 @@ http://www.seva.net/~msolomon/ perl(1), Tie::IxHash(3), LWP::Simple(3), LWP::UserAgent(3). =cut +