File Coverage

blib/lib/WWW/Wunderground/API.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


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