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