File Coverage

blib/lib/Weather/Underground/Forecast.pm
Criterion Covered Total %
statement 5 7 71.4
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 8 10 80.0


line stmt bran cond sub pod time code
1 1     1   37457 use strictures 1;
  1         981  
  1         31  
2             package Weather::Underground::Forecast;
3             BEGIN {
4 1     1   95 $Weather::Underground::Forecast::VERSION = '0.07';
5             }
6 1     1   9516 use Moose;
  0            
  0            
7             use namespace::autoclean;
8             use LWP::Simple;
9             use XML::Simple;
10             use XML::LibXML;
11             use XML::Validate::LibXML;
12              
13             use Data::Dumper::Concise;
14              
15             =head1 Name
16              
17             Weather::Underground::Forecast - Simple API to Weather Underground Forecast Data
18              
19             =head1 Synopsis
20              
21             Get the weather forecast:
22            
23             my $forecast = Weather::Underground::Forecast->new(
24             location => $location,
25             temperature_units => 'fahrenheit', # or 'celsius'
26             );
27            
28             Where the $location can be:
29             * 'city,state' Example: location => 'Bloomington,IN'
30             * zip_code Example: location => 11030
31             * 'latitude,longitude' Example: location => '21.3069444,-157.8583333'
32            
33             my ($highs, $lows) = $forecast->temperatures;
34              
35             NOTE: I<location> is the only required parameter to C<new()>
36              
37              
38             =cut
39              
40             has 'location' => (
41             is => 'rw',
42             isa => 'Str',
43             required => 1,
44             writer => 'set_location',
45             );
46             has 'temperature_units' => (
47             is => 'ro',
48             isa => 'Str',
49             'default' => 'fahrenheit',
50             );
51             has 'data' => (
52             is => 'rw',
53             isa => 'ArrayRef[HashRef]',
54             lazy_build => 1,
55             );
56             has 'raw_data' => (
57             is => 'rw',
58             isa => 'Str',
59             lazy_build => 1,
60             );
61             has 'source_URL' => (
62             is => 'ro',
63             isa => 'Any',
64             'default' => 'http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=',
65             );
66              
67             # When the location changes, we want to clear the data to insure a new data fetch will happen.
68             # We need this since data is lazily built, and we used a distinct name for the writer
69             # so we only clear data when we set the location anytime after initial object construction.
70             after 'set_location' => sub {
71             my $self = shift;
72             $self->clear_data;
73             };
74              
75             =head1 Methods
76              
77             =head2 temperatures
78              
79             Get the high and low temperatures for the number of days specified.
80              
81             Returns: Array of two ArrayRefs being the high and low temperatures
82             Example: my ($highs, $lows) = $wunder->temperatures;
83              
84             =cut
85              
86             sub temperatures {
87             my $self = shift;
88             return ( $self->highs, $self->lows );
89             }
90              
91             =head2 highs
92              
93             Get an ArrayRef[Int] of the forecasted high temperatures.
94              
95             =cut
96              
97             sub highs {
98             my $self = shift;
99              
100             my $key1 = 'high';
101             my $key2 = $self->temperature_units;
102             return $self->_get_forecast_data_by_two_keys( $key1, $key2 );
103             }
104              
105             =head2 lows
106              
107             Get an ArrayRef[Int] of the forecasted low temperatures.
108              
109             =cut
110              
111             sub lows {
112             my $self = shift;
113              
114             my $key1 = 'low';
115             my $key2 = $self->temperature_units;
116             return $self->_get_forecast_data_by_two_keys( $key1, $key2 );
117             }
118              
119             =head2 precipitation
120              
121             Get an ArrayRef[Int] of the forecasted chance of precipitation.
122              
123             Example: my $chance_of_precip = $wunder->precipitation;
124              
125             =cut
126              
127             sub precipitation {
128             my $self = shift;
129              
130             return $self->_get_forecast_data_by_one_key('pop');
131             }
132              
133             # =head2 _get_forecast_data_by_one_key
134              
135             # Get the values for a single forecast metric that is
136             # only one key deep. An examples is: 'pop' (prob. of precip.)
137              
138             # NOTE: One can dump the data attribute to see
139             # the exact data structure and keys available.
140              
141             # =cut
142              
143             sub _get_forecast_data_by_one_key {
144             my ( $self, $key ) = @_;
145              
146             return [ map { $_->{$key} } @{ $self->data } ];
147             }
148              
149             # =head2 _get_forecast_data_by_two_keys
150              
151             # Like the one_key method above but for values that are
152             # two keys deep in the data structure.
153              
154             # =cut
155              
156             sub _get_forecast_data_by_two_keys {
157             my ( $self, $key1, $key2 ) = @_;
158              
159             return [ map { $_->{$key1}->{$key2} } @{ $self->data } ];
160             }
161              
162             sub _query_URL {
163             my $self = shift;
164             return $self->source_URL . $self->location;
165             }
166              
167             # Builders
168              
169             sub _build_data {
170             my $self = shift;
171              
172             my $xml = XML::Simple->new;
173             my $data_ref = $xml->XMLin( $self->raw_data );
174             my $forecasts = $data_ref->{simpleforecast}->{forecastday};
175              
176             return $forecasts;
177             }
178              
179             sub _build_raw_data {
180             my $self = shift;
181              
182             my $content = get( $self->_query_URL );
183             die "Couldn't get URL: ", $self->_query_URL unless defined $content;
184              
185             my $xml_validator = new XML::Validate::LibXML;
186             if ( !$xml_validator->validate($content) ) {
187             my $intro = "Document is invalid\n";
188             my $message = $xml_validator->last_error()->{message};
189             my $line = $xml_validator->last_error()->{line};
190             my $column = $xml_validator->last_error()->{column};
191             die "Error: $intro $message at line $line, column $column";
192             }
193              
194             # return and set attribute to raw xml when we make it this far.
195             return $content;
196             }
197              
198             __PACKAGE__->meta->make_immutable;
199             1
200              
201             __END__
202              
203             =head1 Limitations
204              
205             It is possible that a location could have more than one forecast.
206             The behavior of that possibility has not been tested.
207              
208             =head1 Authors
209              
210             Mateu Hunter C<hunter@missoula.org>
211              
212             =head1 Copyright
213              
214             Copyright 2010, Mateu Hunter
215              
216             =head1 License
217              
218             You may distribute this code under the same terms as Perl itself.
219              
220             =cut