File Coverage

blib/lib/Weather/WWO.pm
Criterion Covered Total %
statement 32 63 50.7
branch 0 8 0.0
condition n/a
subroutine 11 18 61.1
pod 10 10 100.0
total 53 99 53.5


line stmt bran cond sub pod time code
1 1     1   31114 use strictures;
  1         940  
  1         6  
2             package Weather::WWO;
3 1     1   3084 use Moo;
  1         17129  
  1         7  
4 1     1   3150 use MooX::Types::MooseLike::Base qw/Str Int HashRef Bool/;
  1         7848  
  1         117  
5 1     1   1312 use HTTP::Tiny;
  1         64670  
  1         39  
6 1     1   1280 use JSON;
  1         18016  
  1         7  
7              
8             our $VERSION = '0.07';
9              
10             =head1 Name
11              
12             Weather::WWO - API to World Weather Online
13              
14             =head1 Synopsis
15              
16             Get the 5-day weather forecast:
17            
18             my $wwo = Weather::WWO->new( api_key => $your_api_key,
19             use_new_api => 1,
20             location => $location,
21             temperature_units => 'F',
22             wind_units => 'Miles');
23            
24             Where the $location can be:
25             * zip code
26             * IP address
27             * latitude,longitude
28             * City[,State] name
29              
30             my ($highs, $lows) = $wwo->forecast_temperatures;
31              
32             NOTE: I and I are required parameters to C
33             As of May 2013 there is a new API that will replace the old.
34             One can set use_new_api to 1 in the constructor to retrieve from the new api.
35              
36             =cut
37              
38             has 'api_key' => (
39             is => 'ro',
40             isa => Str,
41             required => 1,
42             );
43             has 'location' => (
44             is => 'rw',
45             isa => Str,
46             required => 1,
47             writer => 'set_location',
48             );
49             has 'num_of_days' => (
50             is => 'ro',
51             isa => Int,
52             default => sub { 5 },
53             );
54              
55             # We are only using the JSON format
56             has 'format' => (
57             is => 'ro',
58             isa => Str,
59             default => sub { 'json' },
60             init_arg => undef,
61             );
62             has 'temperature_units' => (
63             is => 'ro',
64             isa => Str,
65             default => sub { 'C' },
66             );
67             has 'wind_units' => (
68             is => 'ro',
69             isa => Str,
70             default => sub { 'Kmph' },
71             );
72             has 'data' => (
73             is => 'rw',
74             isa => HashRef,
75             lazy => 1,
76             builder => '_build_data',
77             );
78             has 'source_URL' => (
79             is => 'lazy',
80             isa => Str,
81             );
82             has 'use_new_api' => (
83             is => 'lazy',
84             isa => Bool,
85             );
86              
87             # When the location changes, we want to clear the data to insure a new data fetch will happen.
88             # We need this since data is lazily built, and we used a distinct name for the writer
89             # so we only clear data when we set the location anytime after initial object construction.
90             after 'set_location' => sub {
91             my $self = shift;
92             $self->clear_data;
93             };
94              
95             =head1 Methods
96              
97             =head2 forecast_temperatures
98              
99             Get the high and low temperatures for the number of days specified.
100              
101             Returns: Array of two ArrayRefs being the high and low temperatures
102             Example: my ($highs, $lows) = $wwo->forecast_temperaures;
103              
104             =cut
105              
106             sub forecast_temperatures {
107 1     1 1 1607 my $self = shift;
108 1         5 return ($self->highs, $self->lows);
109             }
110              
111             =head2 highs
112              
113             Get an ArrayRef[Int] of the forecasted high temperatures.
114              
115             =cut
116              
117             sub highs {
118 1     1 1 2 my $self = shift;
119            
120 1         8 my $high_key = 'tempMax' . $self->temperature_units;
121 1         6 return $self->get_forecast_data_by_key($high_key);
122             }
123              
124             =head2 lows
125              
126             Get an ArrayRef[Int] of the forecasted low temperatures.
127              
128             =cut
129              
130             sub lows {
131 1     1 1 2 my $self = shift;
132            
133 1         5 my $low_key = 'tempMin' . $self->temperature_units;
134 1         2 return $self->get_forecast_data_by_key($low_key);
135             }
136              
137             =head2 winds
138              
139             Get an ArrayRef[Int] of the forecasted wind speeds.
140              
141             =cut
142              
143             sub winds {
144 1     1 1 5 my $self = shift;
145            
146 1         5 my $wind_key = 'windspeed' . $self->wind_units;
147 1         3 return $self->get_forecast_data_by_key($wind_key);
148             }
149              
150             =head2 get_forecast_data_by_key
151              
152             Get the values for a single forecast metric.
153             Examples are: tempMinF, tempMaxC, windspeedMiles etc...
154              
155             NOTE: One can dump the data attribute to see
156             the exact data structure and keys available.
157              
158             =cut
159              
160             sub get_forecast_data_by_key {
161 3     3 1 6 my ($self, $key) = @_;
162            
163 3         4 return [ map { $_->{$key} } @{$self->weather_forecast} ];
  15         56  
  3         5  
164             }
165              
166             =head2 query_string
167              
168             Construct the query string based on object attributes.
169              
170             =cut
171              
172             sub query_string {
173 0     0 1 0 my $self = shift;
174              
175 0         0 my $query_pieces = {
176             q => $self->location,
177             format => $self->format,
178             num_of_days => $self->num_of_days,
179             key => $self->api_key,
180             };
181              
182 0         0 my @query_parts =
183 0         0 map { $_ . '=' . $query_pieces->{$_} } keys %{$query_pieces};
  0         0  
184 0         0 my $query_string = join '&', @query_parts;
185              
186 0         0 return $query_string;
187             }
188              
189             =head2 query_URL
190              
191             Construct the to URL to get by putting the source URL and query_string together.
192              
193             =cut
194              
195             sub query_URL {
196 0     0 1 0 my $self = shift;
197 0         0 return $self->source_URL . '?' . $self->query_string;
198             }
199              
200             =head2 current_conditions
201              
202             The current conditions data structure.
203              
204             =cut
205              
206             sub current_conditions {
207 0     0 1 0 my $self = shift;
208 0         0 return $self->data->{current_condition};
209             }
210              
211             =head2 weather_forecast
212              
213             The weather forecast data structure.
214              
215             =cut
216              
217             sub weather_forecast {
218 3     3 1 5 my $self = shift;
219 3         58 return $self->data->{weather};
220             }
221              
222             =head2 request
223              
224             Information about the request.
225              
226             =cut
227              
228             sub request {
229 0     0 1   my $self = shift;
230 0           return $self->data->{request};
231             }
232              
233             # Builders
234              
235             sub _build_data {
236 0     0     my $self = shift;
237              
238 0           my $URL = $self->query_URL;
239 0           my $response = HTTP::Tiny->new->get($URL);
240 0 0         die "Failed to get $URL\n" unless $response->{success};
241 0           my $content = $response->{content};
242 0 0         die "No content for $URL\n" unless defined $content;
243              
244 0           my $data = decode_json($content);
245             # Are there any errors?
246 0 0         if (my $errors = $data->{data}->{error}) {
247 0           foreach my $error (@{$errors}) {
  0            
248 0           warn $error->{msg};
249             }
250 0           die "Error: Can not get data for location: ", $self->location;
251             }
252              
253 0           return $data->{data};
254             }
255              
256             sub _build_use_new_api {
257 0     0     return 0;
258             }
259              
260             # We currently have two different APIs to choose from
261             # According to WWO, the old API will only work through 31 August 2013
262             sub _build_source_URL {
263 0     0     my $self = shift;
264 0 0         if ($self->use_new_api) {
265 0           return 'http://api.worldweatheronline.com/free/v1/weather.ashx';
266             }
267             else {
268 0           return 'http://free.worldweatheronline.com/feed/weather.ashx';
269             }
270             }
271              
272             1
273              
274             __END__