Permalink
Browse files

Adapted Weather::WWO to Weather::Underground::Forecast

  • Loading branch information...
1 parent 35076e8 commit 1825dc2383e394482f95d6760d51a7dd0f36a5cf @mateu committed Dec 29, 2010
Showing with 254 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +2 −0 Changes
  3. 0 README
  4. +18 −0 dist.ini
  5. +199 −0 lib/Weather/Underground/Forecast.pm
  6. +31 −0 t/basic.t
View
@@ -0,0 +1,4 @@
+.project
+.includepath
+.build
+Weather-Underground-Forecast*
View
@@ -0,0 +1,2 @@
+{{$NEXT}}
+ - Initial Release
View
0 README
No changes.
View
@@ -0,0 +1,18 @@
+name = Weather-Underground-Forecast
+abstract = Weather Underground Forecast Data
+
+[VersionFromModule]
+
+[NextRelease]
+
+[@Git]
+
+[@Basic]
+
+[PodSyntaxTests]
+[PodCoverageTests]
+[CriticTests]
+
+[AutoPrereqs]
+
+
@@ -0,0 +1,199 @@
+package Weather::Underground::Forecast;
+use Moose;
+use namespace::autoclean;
+use LWP::Simple;
+use XML::Simple;
+
+use Data::Dumper::Concise;
+
+our $VERSION = '0.01';
+
+=head1 Name
+
+Weather::Underground::Forecast - Simple API to Weather Underground Forecast Data
+
+=head1 Synopsis
+
+ Get the weather forecast:
+
+ my $forecast = Weather::Underground::Forecast->new(
+ location => $location,
+ temperature_units => 'fahrenheit', # or 'celsius'
+ );
+
+ Where the $location can be:
+ * city,state (Bloomington,IN)
+ * zip code (47401)
+ * latitude,longitude (46,-113)
+
+ my ($highs, $lows) = $forecast->temperatures;
+
+NOTE: I<location> is the only required parameter to C<new()>
+
+
+=cut
+
+has 'location' => (
+ is => 'rw',
+ isa => 'Str',
+ required => 1,
+ writer => 'set_location',
+);
+has 'temperature_units' => (
+ is => 'ro',
+ isa => 'Str',
+ 'default' => 'fahrenheit',
+);
+has 'data' => (
+ is => 'rw',
+ isa => 'ArrayRef[HashRef]',
+ lazy_build => 1,
+);
+has 'source_URL' => (
+ is => 'ro',
+ isa => 'Any',
+ lazy_build => 1,
+);
+
+# When the location changes, we want to clear the data to insure a new data fetch will happen.
+# We need this since data is lazily built, and we used a distinct name for the writer
+# so we only clear data when we set the location anytime after initial object construction.
+after 'set_location' => sub {
+ my $self = shift;
+ $self->clear_data;
+};
+
+=head1 Methods
+
+=head2 temperatures
+
+Get the high and low temperatures for the number of days specified.
+
+ Returns: Array of two ArrayRefs being the high and low temperatures
+ Example: my ($highs, $lows) = $wunder->forecast_temperaures;
+
+=cut
+
+sub temperatures {
+ my $self = shift;
+ return ( $self->highs, $self->lows );
+}
+
+=head2 highs
+
+Get an ArrayRef[Int] of the forecasted high temperatures.
+
+=cut
+
+sub highs {
+ my $self = shift;
+
+ my $key1 = 'high';
+ my $key2 = $self->temperature_units;
+ return $self->get_forecast_data_by_two_keys( $key1, $key2 );
+}
+
+=head2 lows
+
+Get an ArrayRef[Int] of the forecasted low temperatures.
+
+=cut
+
+sub lows {
+ my $self = shift;
+
+ my $key1 = 'low';
+ my $key2 = $self->temperature_units;
+ return $self->get_forecast_data_by_two_keys( $key1, $key2 );
+}
+
+=head2 precipitation
+
+Get an ArrayRef[Int] of the forecasted chance of precipitation.
+
+=cut
+
+sub precipitation {
+ my $self = shift;
+
+ return $self->get_forecast_data_by_one_key('pop');
+}
+
+=head2 get_forecast_data_by_one_key
+
+Get the values for a single forecast metric that is
+only one key deep. An examples is: 'pop' (prob. of precip.)
+
+NOTE: One can dump the data attribute to see
+the exact data structure and keys available.
+
+=cut
+
+sub get_forecast_data_by_one_key {
+ my ( $self, $key ) = @_;
+
+ return [ map { $_->{$key} } @{ $self->data } ];
+}
+
+=head2 get_forecast_data_by_two_keys
+
+Like the one_key method above but for values that are
+two keys deep in the data structure.
+
+=cut
+
+sub get_forecast_data_by_two_keys {
+ my ( $self, $key1, $key2 ) = @_;
+
+ return [ map { $_->{$key1}->{$key2} } @{ $self->data } ];
+}
+
+sub _query_URL {
+ my $self = shift;
+ return $self->source_URL . $self->location;
+}
+
+# Builders
+
+sub _build_data {
+ my $self = shift;
+
+ my $content = get( $self->_query_URL );
+ die "Couldn't get URL: ", $self->_query_URL unless defined $content;
+
+ my $xml = XML::Simple->new;
+ my $data_ref = $xml->XMLin($content);
+ my $forecasts = $data_ref->{simpleforecast}->{forecastday};
+
+ return $forecasts;
+}
+
+sub _build_source_URL {
+ my $self = shift;
+ return
+ 'http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=';
+}
+
+__PACKAGE__->meta->make_immutable;
+1
+
+__END__
+
+=head1 Limitations
+
+It is possible that location could have more than one forecast.
+The behavior of that possibility has not been tested.
+
+=head1 Authors
+
+Mateu Hunter C<hunter@missoula.org>
+
+=head1 Copyright
+
+Copyright 2010, Mateu Hunter
+
+=head1 License
+
+You may distribute this code under the same terms as Perl itself.
+
+=cut
View
@@ -0,0 +1,31 @@
+use strict;
+use warnings;
+use Test::More;
+use Weather::Underground::Forecast;
+use LWP::Simple;
+use Data::Dumper::Concise;
+
+my $wunder_forecast = Weather::Underground::Forecast->new(
+ location => 'Missoula,MT',
+ temperature_units => 'fahrenheit', # or 'celsius'
+);
+
+isa_ok( $wunder_forecast, 'Weather::Underground::Forecast' );
+can_ok( 'Weather::Underground::Forecast', ( 'temperatures', 'precipitation' ) );
+
+SKIP:
+{
+
+ # Test internet connection
+ my $source_URL = $wunder_forecast->_query_URL;
+ my $content = get($source_URL);
+ skip( 'Skipping live test using Internet', 3 ) if !$content;
+
+ my ( $highs, $lows ) = $wunder_forecast->temperatures;
+ my $chance_of_precip = $wunder_forecast->precipitation;
+ is( ref($highs), 'ARRAY', 'highs data structure' );
+ is( ref($lows), 'ARRAY', 'lows data structure' );
+ is( ref($chance_of_precip), 'ARRAY', 'precips data structure' );
+}
+
+done_testing();

0 comments on commit 1825dc2

Please sign in to comment.