File Coverage

blib/lib/WWW/Wunderground/API.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package WWW::Wunderground::API;
2              
3 1     1   22328 use 5.006;
  1         3  
  1         37  
4 1     1   1016 use Moo;
  1         21027  
  1         5  
5 1     1   2307 use LWP::Simple;
  1         129471  
  1         10  
6 1     1   985 use XML::Simple;
  0            
  0            
7             use JSON::Any;
8             use Hash::AsObject;
9              
10             =head1 NAME
11              
12             WWW::Wunderground::API - Use Weather Underground's JSON/XML API
13              
14             =head1 VERSION
15              
16             Version 0.06
17              
18             =cut
19              
20             our $VERSION = '0.06';
21              
22             has location => (is=>'rw', required=>1);
23             has api_key => (is=>'ro', default=>sub { $ENV{WUNDERGROUND_API} });
24             has api_type => (is=>'rw', lazy=>1, default=>sub { $_[0]->api_key ? 'json' : 'xml' });
25             has cache => (is=>'ro', lazy=>1, default=>sub { new WWW::Wunderground::API::BadCache });
26             has auto_api => (is=>'ro', default=> sub {0} );
27             has raw => (is=>'rw', default=>sub{''});
28             has data => (is=>'rw', lazy=>1, default=>sub{ Hash::AsObject->new } );
29              
30             sub json {
31             my $self = shift;
32             return $self->api_type eq 'json' ? $self->raw : undef;
33             }
34              
35             sub xml {
36             my $self = shift;
37             return $self->api_type eq 'xml' ? $self->raw : undef;
38             }
39              
40              
41             sub update {
42             my $self = shift;
43             if ($self->api_key) {
44             $self->api_call('conditions');
45             } else {
46             my $legacy_url = 'http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query='.$self->location;
47             my $xml;
48             unless($xml = $self->cache->get($legacy_url)) {
49             $xml = get($legacy_url);
50             $self->cache->set($legacy_url,$xml);
51             }
52             if ($xml) {
53             $self->raw($xml);
54             $self->data(Hash::AsObject->new({conditions=>XMLin($xml)}));
55             }
56             }
57             }
58              
59             sub _guess_key {
60             my $self = shift;
61             my ($struc,$action) = @_;
62              
63             #try to guess result structure key
64             return $action if defined($struc->{$action});
65             foreach my $key (keys %$struc) {
66             next if $key=~ /(response|features|version|termsofservice)/i;
67             return $key;
68             }
69             }
70              
71             sub api_call {
72             my $self = shift;
73             my ($action, $location) = @_;
74             $location||=$self->location;
75             if ($self->api_key) {
76             my $base = 'http://api.wunderground.com/api';
77             my $url = join('/', $base,$self->api_key,$action,'q',$location).'.'.$self->api_type;
78              
79             my $result;
80             unless ($result = $self->cache->get($url)) {
81             $result = get($url);
82             $self->cache->set($url,$result);
83             }
84              
85             $self->raw($result);
86            
87             my $struc = $self->api_type eq 'json'
88             ? JSON::Any->jsonToObj($self->raw)
89             : XMLin($self->raw);
90              
91             my $action_key = $self->_guess_key($struc,$action);
92              
93             $struc = $struc->{$action_key} if $action_key;
94             $self->data->{$action} = $struc;
95              
96             return new Hash::AsObject($struc);
97             } else {
98             warn "Only basic weather conditions are supported using the deprecated keyless interface";
99             warn "please visit http://www.wunderground.com/weather/api to obtain your own API key";
100             }
101             }
102              
103              
104             around BUILDARGS => sub {
105             my $orig = shift;
106             my $class = shift;
107             if (@_ == 1 and !ref($_[0])) {
108             return $class->$orig( location=>$_[0] );
109             } else {
110             return $class->$orig(@_);
111             }
112             };
113              
114             sub AUTOLOAD {
115             my $self = shift;
116             our $AUTOLOAD;
117             my ($key) = $AUTOLOAD =~ /::(\w+)$/;
118             my $val = $self->data->$key;
119              
120             unless ($val) {
121             $self->update if ($self->auto_api and !$self->data->conditions);
122             $val = $self->data->conditions->$key if $self->data->conditions;
123             }
124              
125             if (defined($val)) {
126             return $val;
127             } else {
128             return $self->api_call($key) if $self->auto_api;
129             warn "$key is not defined. Is it a valid key, and is data actually loading?";
130             warn "If you're trying to autoload an endpoint, set auto_api to something truthy";
131             return undef;
132             }
133             }
134              
135             sub DESTROY {}
136              
137             __PACKAGE__->meta->make_immutable;
138              
139              
140             #The following exists purely as an example for others of what not to do.
141             #Use a Cache::Cache or CLI Cache. Really.
142             package WWW::Wunderground::API::BadCache;
143             use Moo;
144              
145             has store=>(is=>'rw', lazy=>1, default=>sub{{}});
146              
147             sub get {
148             my $self = shift;
149             my ($key) = @_;
150             if (exists($self->store->{$key})) {
151             return $self->store->{$key};
152             }
153             return undef;
154             }
155              
156             sub set {
157             my $self = shift;
158             my ($key, $val) = @_;
159             $self->store->{$key} = $val;
160             return $val;
161             }
162              
163              
164             =head1 SYNOPSIS
165              
166             Connects to the Weather Underground JSON/XML service and parses the data
167             into something usable. The entire response is available in Hash::AsObject form, so
168             any data that comes from the server is accessible. Print a Data::Dumper of ->data
169             to see all of the tasty data bits.
170              
171             use WWW::Wunderground::API;
172              
173             #location
174             my $wun = new WWW::Wunderground::API('Fairfax, VA');
175              
176             #or zipcode
177             my $wun = new WWW::Wunderground::API('22030');
178              
179             #or airport identifier
180             my $wun = new WWW::Wunderground::API('KIAD');
181              
182             #using the options
183              
184             my $wun = new WWW::Wunderground::API(
185             location=>'22152',
186             api_key=>'my wunderground api key',
187             auto_api=>1,
188             cache=>Cache::FileCache->new({ namespace=>'wundercache', default_expires_in=>2400 }) #A cache is probably a good idea.
189             );
190              
191            
192             #Check the wunderground docs for details, but here are just a few examples
193             print 'The temperature is: '.$wun->conditions->temp_f."\n";
194             print 'The rest of the world calls that: '.$wun->conditions->temp_c."\n";
195             print 'Record high temperature year: '.$wun->almanac->temp_high->recordyear."\n";
196             print "Sunrise at:".$wun->astronomy->sunrise->hour.':'.$wun->astronomy->sunrise->minute."\n";
197             print "Simple forecast:".$wun->forecast->simpleforecast->forecastday->[0]{conditions}."\n";
198             print "Text forecast:".$wun->forecast->txt_forecast->forecastday->[0]{fcttext}."\n";
199             print "Long range forecast:".$wun->forecast10day->txt_forecast->forecastday->[9]{fcttext}."\n";
200             print "Chance of rain three hours from now:".$wun->hourly->[3]{pop}."%\n";
201             print "Nearest airport:".$wun->geolookup->nearby_weather_stations->airport->{station}[0]{icao}."\n";
202              
203             #Conditions is autoloaded into the root of the object
204             print "Temp_f:".$wun->temp_f."\n";
205              
206             =head1 METHODS/ACCESSORS
207              
208             =head2 update()
209              
210             Included for backward compatibility only.
211             Refetches conditions data from the server. It will be removed in a future release.
212             If you specify an api_key then this is equvilent of ->api_call('conditions') and is subject to the same cache
213              
214             =head2 location()
215              
216             Set the location. For example:
217              
218             my $wun = new WWW::Wunderground::API('22030');
219             my $ffx_temp = $wun->conditions->temp_f;
220              
221             $wun->location('KJFK');
222             my $ny_temp = $wun->conditions->temp_f;
223              
224             $wun->location('San Diego, CA');
225             my $socal_temp = $wun->conditions->temp_f;
226              
227             Valid locations can be derived from others by calling the geolookup endpoint, but you probably already know where you are.
228              
229              
230             =head2 auto_api
231              
232             set auto_api to something truthy to have the module automatically make API calls without the use of api_call()
233              
234              
235             =head2 api_call( api_name, )
236              
237             Set api_name to any location-based wunderground api call (almanac,conditions,forecast,history...).
238              
239             Location is optional and defaults to L. Can be any valid location (eg 22152,'KIAD','q/CA/SanFrancisco',...)
240              
241             #Almanac data for 90210
242             $wun->api_call('almanac','90210');
243              
244             #If auto_api=>1 the following is equivalent
245             $wun->location(90120);
246             $wun->almanac;
247              
248             #10 day forecast for New York
249             $wun->api_call('forecast10day'','KJFK');
250              
251              
252              
253             =head2 raw()
254              
255             Returns raw text result from the most recent API call. This will be either xml or json depending on api_type.
256             You can also set this to whatever json/xml you'd like, though I can't imagine why you'd want to.
257              
258             =head2 cache()
259              
260             Specify a cache object. Needs only to support get(key) and set (key,value) so L or L caches should work.
261              
262             =head2 xml()
263              
264             *Deprecated* - use L instead.
265              
266             Returns raw xml result from wunderground server where applicable
267              
268              
269             =head2 json()
270              
271             *Deprecated* - use L instead.
272              
273             Returns raw json result from wunderground server where applicable
274              
275             =head2 data()
276              
277             Contains all weather data from server parsed into convenient L form;
278              
279             =head2 api_key()
280              
281             Required for JSON api access. Defaults to $ENV{WUNDERGROUND_API}
282              
283             =head2 api_type()
284              
285             Defaults to json. If no api_key is specified it will be set to xml and only basic weather conditions will be available.
286              
287             =head1 AUTHOR
288              
289             John Lifsey, C<< >>
290              
291             =head1 BUGS
292              
293             Please report any bugs or feature requests to C, or through
294             the web interface at L. I will be notified, and then you'll
295             automatically be notified of progress on your bug as I make changes.
296              
297             =head1 SOURCE
298              
299             Better yet, fork on github and send me a pull request:
300             L
301              
302              
303             =head1 SUPPORT
304              
305             You can find documentation for this module with the perldoc command.
306              
307             perldoc WWW::Wunderground::API
308              
309              
310             You can also look for information at:
311              
312             =over 4
313              
314             =item * RT: CPAN's request tracker (report bugs here)
315              
316             L
317              
318             =item * AnnoCPAN: Annotated CPAN documentation
319              
320             L
321              
322             =item * CPAN Ratings
323              
324             L
325              
326             =item * Search CPAN
327              
328             L
329              
330             =back
331              
332              
333             =head1 LICENSE AND COPYRIGHT
334              
335             Copyright 2013 John Lifsey.
336              
337             This program is free software; you can redistribute it and/or modify it
338             under the terms of either: the GNU General Public License as published
339             by the Free Software Foundation; or the Artistic License.
340              
341             See http://dev.perl.org/licenses/ for more information.
342              
343              
344             =cut
345              
346             __PACKAGE__->meta->make_immutable;
347             1; # End of WWW::Wunderground::API