File Coverage

blib/lib/Weather/Underground.pm
Criterion Covered Total %
statement 132 271 48.7
branch 59 136 43.3
condition 58 96 60.4
subroutine 9 10 90.0
pod 2 3 66.6
total 260 516 50.3


line stmt bran cond sub pod time code
1             package Weather::Underground;
2              
3 1     1   607 use strict;
  1         2  
  1         31  
4 1     1   4 use vars qw($VERSION $CGI $CGIVAR $MYNAME $DEBUG %MODULES);
  1         1  
  1         74  
5 1     1   668 use LWP::Simple qw($ua get);
  1         146110  
  1         7  
6 1     1   988 use HTML::TokeParser;
  1         13154  
  1         39  
7 1     1   11 use Fcntl qw(:flock);
  1         2  
  1         4394  
8              
9             $VERSION = '3.03';
10              
11             #
12             # GLOBAL Variables Assignments
13             #
14              
15             $CGI = 'http://mobile.wunderground.com/cgi-bin/findweather/getForecast';
16             $CGIVAR = 'query';
17             $MYNAME = "Weather::Underground";
18             $DEBUG = 0;
19              
20             %MODULES = (
21             "Data::Dumper" => 0,
22             "Storable" => 0,
23             "FreezeThaw" => 0,
24             );
25              
26             foreach (keys %MODULES) {
27             eval { eval("require $_;") || die "$_ not found"; };
28             $MODULES{$_} = $@ ? 0 : 1;
29             }
30              
31             =head1 NAME
32              
33             Weather::Underground - Perl extension for retrieving weather information from wunderground.com
34              
35             =head1 SYNOPSIS
36              
37             use Weather::Underground;
38              
39             $weather = Weather::Underground->new(
40             place => "Montreal, Canada",
41             debug => 0,
42             )
43             || die "Error, could not create new weather object: $@\n";
44              
45             $arrayref = $weather->get_weather()
46             || die "Error, calling get_weather() failed: $@\n";
47              
48             foreach (@$arrayref) {
49             print "MATCH:\n";
50             while (($key, $value) = each %{$_}) {
51             print "\t$key = $value\n";
52             }
53             }
54              
55             =head1 DESCRIPTION
56              
57             Weather::Underground is a perl module which provides a simple OO interface to retrieving weather data for a geographic location. It does so by querying wunderground.com and parsing the returned results.
58              
59             =head1 CONSTRUCTOR
60              
61             =over 4
62              
63             =item new(hash or hashref);
64              
65             Creates and returns a new Weather::Underground object.
66              
67             Takes either a hash (as the SYNOPSIS shows) or a hashref
68              
69             Required keys in the hash:
70              
71             =over 4
72              
73             =item place
74              
75             This key should be assigned the value of the geographical place you would like to retrieve the weather information for. The format of specifying the place really depends on wunderground.com more than it depends on this perl module, however at the time of this writing they accept 'City', 'City, State', 'State', 'State, Country' and 'Country'.
76              
77             =back
78              
79             Optional keys in the hash:
80              
81             =over 4
82              
83             =item cache_file
84              
85             This key should be assigned a file name to use as a cache. The module will store and use data from that file instead of querying wunderground.com if cache_max_age has not been exceeded.
86              
87             This key is ignored if the cache_max_age key is not supplied.
88              
89             =item cache_max_age
90              
91             This key should be assigned a numeric value which is the number of seconds after which any data in the cache_file will be considered too old and a new request will be made to wunderground.com
92              
93             This key is ignored if the cache_file key is not supplied.
94              
95             =item debug
96              
97             This key should be set to a true or false false. A false value means no debugging information will be printed, a true value means debug information will be printed.
98              
99             =item timeout
100              
101             If the default timeout for the LWP::UserAgent request (180 seconds at the time of this writing) is not enough for you, you can change the timeout by providing this key. It should contain the timeout for the HTTP request seconds in seconds.
102              
103             =back
104              
105             =back
106              
107             =head1 METHODS
108              
109             =over 4
110              
111             =item get_weather()
112              
113             This method is used to initiate the connection to wunderground.com, query their system, and parse the results or retrieve the results from the cache_file constructor key if appropriate.
114              
115             If no results are found, returns undef.
116              
117             If results are found, returns an array reference. Each element in the array is a hash reference. Each hash contains information about a place that matched the query;
118              
119             Each hash contains the following keys:
120              
121             =over 4
122              
123             =item place
124              
125             (the exact place that was matched)
126              
127             =item temperature_celsius
128              
129             (the temperature in celsius)
130              
131             =item temperature_fahrenheit
132              
133             (the temperature in fahrenheit)
134              
135             =item humidity
136              
137             (humidity percentage)
138              
139             =item conditions
140              
141             (current sky, example: 'Partly cloudy')
142              
143             =item wind_direction
144              
145             (wind direction, example: "North")
146              
147             =item wind_milesperhour
148              
149             (wind speed in miles per hour)
150              
151             =item wind_kilometersperhour
152              
153             (wind speed in kilometers per hour)
154              
155             =item pressure
156              
157             (the barometric pressure)
158              
159             =item updated
160              
161             (when the content was last updated on the server)
162              
163             =item clouds
164              
165             (description of clouds)
166              
167             =item dewpoint_celsius
168              
169             (the dew point in celsius)
170              
171             =item dewpoint_fahrenheit
172              
173             (the dew point in fahrenheit)
174              
175             =item moonphase
176              
177             (phase of the moon, example: "Full Moon")
178              
179             =item moonrise
180              
181             (time of moon rise, including timezone)
182              
183             =item moonset
184              
185             (time of moon setting, including timezone)
186              
187             =item sunrise
188              
189             (time of sun rising, including timezone)
190              
191             =item sunset
192              
193             (time of sun setting, including timezone)
194              
195             =item visibility_miles
196              
197             (visibility in miles)
198              
199             =item visibility_kilometers
200              
201             (visibility in kilometers)
202              
203             =back
204              
205             =back
206              
207             =head1 NOTICE
208              
209             =over 4
210              
211             =item 1
212              
213             Your query may result in more than 1 match. Each match is a hash reference added as a new value in the array which get_weather() returns the reference to.
214              
215             =item 2
216              
217             Due to the differences between single and multiple-location matches, some of the keys listed above may not be available in multi-location matches.
218              
219             =back
220              
221             =head1 EXAMPLES
222              
223             =over 4
224              
225             =item Example 1: Print all matching information
226              
227             See SYNOPSIS
228              
229             =item Example 2: Print the Celsius temperature of the first matching place
230              
231             use Weather::Underground;
232              
233             $weather = Weather::Underground->new(
234             place => "Montreal",
235             debug => 0
236             )
237             || die "Error, could not create new weather object: $@\n";
238              
239             $arrayref = $weather->get_weather()
240             || die "Error, calling get_weather() failed: $@\n";
241              
242             print "The celsius temperature at $arrayref->[0]->{place} is $arrayref->[0]->{temperature_celsius}\n";
243              
244             =back
245              
246             =head1 ERRORS
247              
248             All methods return something that evaluates to true when successful, or undef when not successful.
249              
250             If the constructor or a method returns undef, the variable $@ will contain a text string containing the error that occurred.
251              
252             =head1 AUTHOR
253              
254             Mina Naguib
255             http://mina.naguib.ca
256             mnaguib@cpan.org
257              
258             =head1 COPYRIGHT
259              
260             Copyright (C) 2002-2005 Mina Naguib. All rights reserved. Use is subject to the Perl license.
261              
262             =cut
263              
264             #
265             # Public methods:
266             #
267              
268             sub new {
269 2     2 1 131 my $class = shift;
270 2         5 my $self;
271             my %parameters;
272 0         0 my $module;
273 0         0 my $raw;
274 0         0 my $cache;
275 2         7 local (*FH);
276              
277 2 50       8 if (ref($_[0]) eq "HASH") {
278 0         0 %parameters = %{ $_[0] };
  0         0  
279             }
280             else {
281 2         11 %parameters = @_;
282             }
283              
284 2         6 $DEBUG = $parameters{debug};
285 2         13 _debug("Creating a new $MYNAME object");
286 2 50       8 if (!$parameters{place}) {
287 0         0 _debug("ERROR: Location not specified");
288 0         0 return undef;
289             }
290             $self = {
291 2         18 "place" => $parameters{place},
292             "timeout" => $parameters{timeout},
293             "_url" => $CGI . '?' . $CGIVAR . '=' . $parameters{place}
294             };
295 2 50 33     23 if ($parameters{cache_max_age} && $parameters{cache_file}) {
    50 33        
296              
297             #
298             # We've been requested to use caching - let's do sanity, then populate $module and $cache
299             #
300 0 0       0 if (!grep { $_ } values %MODULES) {
  0         0  
301 0         0 _debug("Error: Can not use cache_file when none of the needed serialization modules (" . join(" or ", keys %MODULES) . ") are installed");
302 0         0 return undef;
303             }
304 0 0       0 if ($parameters{cache_max_age} !~ /^[0-9.]+$/) {
305 0         0 _debug("Error: Supplied cache_max_age key must be a number");
306 0         0 return undef;
307             }
308 0 0       0 if (-f $parameters{cache_file}) {
309              
310             #
311             # The cache file already exists
312             #
313 0 0       0 if (!open(FH, $parameters{cache_file})) {
314 0         0 _debug("Error: Failed to open $parameters{cache_file} for reading: $!");
315 0         0 return undef;
316             }
317 0 0       0 if (!flock(FH, LOCK_EX)) {
318 0         0 close(FH);
319 0         0 _debug("Error: Failed to obtain an exclusive lock on $parameters{cache_file}: $!");
320 0         0 return undef;
321             }
322 0 0       0 if (!seek(FH, 0, 0)) {
323 0         0 flock(FH, LOCK_UN);
324 0         0 close(FH);
325 0         0 _debug("Error: Failed to seek to the beginning of $parameters{cache_file}: $!");
326 0         0 return undef;
327             }
328 0         0 $module = ;
329 0         0 chomp $module;
330 0 0       0 if (!exists $MODULES{$module}) {
    0          
331 0         0 flock(FH, LOCK_UN);
332 0         0 close(FH);
333 0         0 _debug("cache_file $parameters{cache_file} does not appear to be a valid Weather::Underground cache file");
334 0         0 return undef;
335             }
336             elsif (!$MODULES{$module}) {
337 0         0 flock(FH, LOCK_UN);
338 0         0 close(FH);
339 0         0 _debug("cache_file $parameters{cache_file} with serialization module $module which is not installed on this machine. Please install it or delete the cache file to start with a fresh one");
340 0         0 return undef;
341             }
342              
343 0         0 $cache = "";
344 0         0 $raw = "";
345 0         0 while () {
346 0         0 $raw .= $_;
347             }
348 0         0 flock(FH, LOCK_UN);
349 0         0 close(FH);
350              
351             #
352             # Now deserialize $cache
353             #
354 0 0       0 if ($module eq "Data::Dumper") {
    0          
    0          
355 0         0 my $VAR1;
356 0         0 $cache = eval($raw);
357             }
358             elsif ($module eq "Storable") {
359 0         0 $cache = Storable::thaw($raw);
360             }
361             elsif ($module eq "FreezeThaw") {
362 0         0 $cache = FreezeThaw::thaw($raw);
363             }
364              
365 0 0       0 if (ref($cache) ne "HASH") {
366 0         0 _debug("Failed to deserialize cache with module $module - [$!] [$@] got non-hashref [$cache] from raw [$raw]");
367 0         0 return undef;
368             }
369             }
370             else {
371              
372             #
373             # The cache file does not exist - create new one
374             #
375 0 0       0 if (!open(FH, ">$parameters{cache_file}")) {
376 0         0 _debug("Error: Failed to open $parameters{cache_file} for writing: $!");
377 0         0 return undef;
378             }
379 0         0 close(FH);
380 0         0 $module = (sort grep { $MODULES{$_} } keys %MODULES)[0];
  0         0  
381 0         0 $cache = {};
382             }
383              
384             #
385             # If we've reached here, cache_file and cache_max_age are good
386             #
387 0         0 $self->{cache_file} = $parameters{cache_file};
388 0         0 $self->{cache_max_age} = $parameters{cache_max_age};
389 0         0 $self->{_cache_module} = $module;
390 0         0 $self->{_cache_cache} = $cache;
391             }
392             elsif ($parameters{cache_max_age} || $parameters{cache_file}) {
393 0         0 _debug("cache_max_age or cache_file was supplied without the other - ignoring it");
394             }
395              
396 2         5 bless($self, $class);
397 2         9 return $self;
398             }
399              
400             # legacy:
401             sub getweather {
402 0     0 0 0 return get_weather(@_);
403             }
404              
405             sub get_weather {
406 2     2 1 58 my ($self) = @_;
407 2         3 my $document;
408             my $parser;
409 0         0 my $token;
410 0         0 my %state;
411 0         0 my $text;
412 2         5 my $arrayref = [];
413 2         3 my $oldagent;
414 2         4 local (*FH);
415              
416 2         9 _debug("Getting weather info for " . $self->{place});
417              
418 2 50       7 if ($self->{_cache_cache}) {
419              
420             #
421             # We have a cache
422             #
423 0         0 _debug("Checking cache");
424 0 0       0 if (exists $self->{_cache_cache}->{ $self->{place} }) {
425 0 0       0 if ((time - $self->{_cache_cache}->{ $self->{place} }->{"time"}) <= $self->{cache_max_age}) {
426 0         0 _debug("Found in cache within cache_max_age");
427 0         0 return $self->{_cache_cache}->{ $self->{place} }->{"arrayref"};
428             }
429             else {
430 0         0 _debug("Found in cache but too old");
431             }
432             }
433             }
434              
435 2         9 _debug("Retrieving url " . $self->{_url});
436              
437 2 50       7 if ($self->{timeout}) {
438 0         0 _debug("Setting timeout for LWP::Simple's LWP::UserAgent object to $self->{timeout}");
439 0         0 $ua->timeout($self->{timeout});
440             }
441 2         18 $oldagent = $ua->agent();
442 2         158 $ua->agent("Weather::Underground version $VERSION");
443 2         121 $document = get($self->{_url});
444 2         2195305 $ua->agent($oldagent);
445              
446 2 50       162 if (!$document) {
447 0         0 _debug("Could not retrieve HTML document " . $self->{_url});
448 0         0 return undef;
449             }
450             else {
451 2         61 _debug("I retrieved the following data:\n\n\n\n\n$document\n\n\n\n\n");
452             }
453              
454             #
455             # Some minor cleanup to preserve our sanity and regexes:
456             #
457 2         483 $document =~ s/<\/?[bi]>//gi;
458 2         1894 $document =~ s/
/\n/gi;
459 2         55 _debug("After cleanup, document data:\n\n\n\n\n$document\n\n\n\n\n");
460              
461 2         7 _debug("Beginning parsing");
462 2 50       27 unless ($parser = HTML::TokeParser->new(\$document)) {
463 0         0 _debug("Failed to create parser object");
464 0         0 return undef;
465             }
466              
467 2 100       2711 if ($document =~ /observed at/i) {
468              
469             #
470             # Single-location match
471             #
472              
473 1         4 _debug("Single-location result detected");
474              
475 1         11 while ($token = $parser->get_token) {
476 327 100 100     6461 if ($token->[0] eq "T" && !$token->[2]) {
    100 100        
    100 100        
    100 100        
    100 100        
      100        
      100        
      100        
      100        
      66        
477              
478             #
479             # The beginning of a text token - retrieve the whole thing and clean it up
480             #
481 136         380 $text = $token->[1] . $parser->get_text();
482 136         3716 $text =~ s/&#([0-9]{1,3});/chr($1)/ge;
  0         0  
483 136         207 $text =~ s/ / /gi;
484 136         443 $text =~ s/\s+/ /g;
485 136         389 $text =~ s/^\s+//;
486 136         306 $text =~ s/\s+$//;
487 136 100       479 next if $text !~ /[a-z0-9]/i;
488 56 50       106 next if $text eq "IMG";
489              
490 56 100       180 if ($state{"inheader"}) {
    100          
491              
492             #
493             # Text in the header
494             #
495 2         8 _debug("Matched text in header [$text]");
496              
497 2 100       16 if ($text =~ /updated\s*:?\s*(.+?)\s*$/i) {
498 1         6 _debug("Matched key UPDATED [$1]");
499 1         3 $state{"content_UPDATED"} = $1;
500             }
501 2 100       14 if ($text =~ /observed\s+at\s+:?\s*(.+)/i) {
502 1         6 _debug("Matched key PLACE [$1]");
503 1         6 $state{"content_PLACE"} = $1;
504             }
505              
506             }
507             elsif ($state{"incontent"}) {
508              
509             #
510             # This is either a header or a content, depending on the column number
511             #
512 51 100       240 if ($state{"contentnumber"} == 1) {
513              
514             #
515             # It's a header - remember to associate the upcoming content under it
516             #
517 18         49 _debug("Read header text [$text]");
518 18         23 my $h = $text;
519 18         41 $h =~ s/\s/_/g;
520 18         71 $state{"lastcontentheader"} = uc($h);
521             }
522             else {
523              
524             #
525             # It's a content - associate it with the previous header
526             #
527 33         86 _debug("Read content text [$text]");
528 33 100       99 if (exists $state{"content_$state{lastcontentheader}"}) {
529 15         29 $state{"content_$state{lastcontentheader}"} .= " ";
530             }
531 33         144 $state{"content_$state{lastcontentheader}"} .= $text;
532             }
533             }
534             }
535             elsif ($state{"interesting"} && $token->[0] eq "S" && uc($token->[1]) eq "TR") {
536              
537             #
538             # Some interesting row starting
539             #
540 19         35 _debug("Interesting row started");
541              
542 19 100 100     96 if (!$state{"inheader"} && !$state{"incontent"}) {
    100          
543              
544             #
545             # First interesting row is header
546             #
547 1         3 $state{"inheader"} = 1;
548 1         12 _debug("Entered header");
549             }
550             elsif ($state{"inheader"}) {
551              
552             #
553             # New row means we are no longer in header
554             #
555 1         2 $state{"inheader"} = 0;
556 1         3 $state{"incontent"} = 1;
557 1         4 _debug("Entered first row of content");
558             }
559             else {
560              
561             #
562             # A new header+content coming up
563             #
564 17         30 _debug("New content row starting");
565 17         53 $state{"contentnumber"} = 0;
566             }
567             }
568             elsif ($token->[0] eq "S" && uc($token->[1]) eq "TD" && $state{"incontent"}) {
569              
570             #
571             # A new header or content cell is starting
572             #
573 36         63 _debug("New header or content cell starting");
574 36         106 $state{"contentnumber"}++;
575             }
576             elsif (!$state{"interesting"} && $token->[0] eq "S" && uc($token->[1]) eq "TABLE" && $token->[2]->{border} == 1) {
577              
578 1         4 _debug("Entered the interesting table");
579 1         3 $state{"interesting"} = 1;
580 1         3 $state{"inheader"} = 0;
581 1         4 $state{"incontent"} = 0;
582             }
583             elsif ($token->[0] eq "E" && uc($token->[1]) eq "TABLE" && $state{"interesting"}) {
584              
585             #
586             # Main table closed - Done parsing - save the data
587             #
588 1         4 _debug("Main table closed - end of interesting data");
589 1         5 _state2result(\%state, $arrayref);
590              
591             #
592             # No need to keep going - it's only 1 location
593             #
594 1         2 last;
595             }
596             }
597             }
598             else {
599              
600             #
601             # Multi-location match
602             #
603 1         5 _debug("Multi-location result detected");
604              
605 1         8 while ($token = $parser->get_token) {
606              
607 442 50 100     10905 if ($token->[0] eq "T" && !$token->[2] && $state{"interesting"}) {
    100 66        
    50 66        
    50 100        
    50 33        
      33        
      33        
      33        
      33        
      33        
608              
609             #
610             # The beginning of a text token - retrieve the whole thing and clean it up
611             #
612 0         0 $text = $token->[1] . $parser->get_text();
613 0         0 $text =~ s/&#([0-9]{1,3});/chr($1)/ge;
  0         0  
614 0         0 $text =~ s/ / /gi;
615 0 0       0 next if $text !~ /[a-z0-9]/i;
616 0         0 $text =~ s/^\s+//g;
617 0         0 $text =~ s/\s+$//g;
618 0         0 $text =~ s/\s+/ /g;
619              
620 0 0       0 if ($state{"incontent"}) {
621              
622             #
623             # This is content we're interested in - store it under the header title of the same column number
624             #
625 0         0 _debug("Content text read [$text]");
626 0 0       0 if (exists $state{"content"}) {
627 0         0 $state{"content"} .= ":";
628             }
629 0         0 $state{"content"} .= $text;
630             }
631              
632             }
633             elsif (!$state{"interesting"} && $token->[0] eq "S" && uc($token->[1]) eq "TABLE") {
634 1         5 $state{"tablenumber"}++;
635 1 50       7 if ($state{"tablenumber"} == 2) {
636              
637             #
638             # Second table is where the data is
639             #
640 0         0 _debug("Entered interesting table");
641 0         0 $state{"interesting"} = 1;
642             }
643             }
644             elsif ($state{"interesting"} && $token->[0] eq "S" && uc($token->[1]) eq "TR") {
645              
646 0         0 _debug("Interesting row started");
647              
648 0 0 0     0 if (!$state{"inheader"} && !$state{"incontent"}) {
    0          
649 0         0 _debug("Entered header");
650 0         0 $state{"inheader"} = 1;
651             }
652             elsif ($state{"inheader"}) {
653 0         0 _debug("Entered content");
654 0         0 $state{"inheader"} = 0;
655 0         0 $state{"incontent"} = 1;
656             }
657             }
658             elsif ($state{"incontent"} && $token->[0] eq "E" && uc($token->[1]) eq "TR") {
659              
660             #
661             # End of a content row
662             #
663 0         0 _debug("End of a content row. Parsing [$state{content}].");
664 0         0 ($state{"content_PLACE"}) = $state{"content"} =~ /^(.+?)\s*:/;
665 0         0 ($state{"content_TEMPERATURE"}) = $state{"content"} =~ /\s*:\s*(.+)$/;
666 0         0 delete $state{"content"};
667 0         0 _state2result(\%state, $arrayref);
668             }
669             elsif ($state{"interesting"} && $token->[0] eq "E" && uc($token->[1]) eq "TABLE") {
670              
671             #
672             # The table that has the data is finished - no need to keep parsing
673             #
674 0         0 _debug("End of table while we're in table - we're done");
675 0         0 last;
676             }
677             }
678              
679             }
680              
681 2 100       18 if (!@$arrayref) {
682 1         6 _debug("No matching places found");
683 1         18 return undef;
684             }
685             else {
686 1 50       5 if ($self->{cache_file}) {
687              
688             #
689             # Let's save the result into the cache_file before we return it
690             #
691 0         0 _debug("Saving results into cache_file $self->{cache_file}");
692              
693 0         0 $self->{_cache_cache}->{ $self->{place} } = {
694             "time" => time,
695             "arrayref" => $arrayref,
696             };
697 0 0       0 if (open(FH, ">$self->{cache_file}")) {
698 0 0       0 if (flock(FH, LOCK_EX)) {
699 0 0       0 if (seek(FH, 0, 0)) {
700 0         0 print FH $self->{_cache_module};
701 0         0 print FH "\n";
702 0 0       0 if ($self->{_cache_module} eq "Data::Dumper") {
    0          
    0          
703 0         0 print FH Data::Dumper::Dumper($self->{_cache_cache});
704             }
705             elsif ($self->{_cache_module} eq "Storable") {
706 0         0 print FH Storable::freeze($self->{_cache_cache});
707             }
708             elsif ($self->{_cache_module} eq "FreezeThaw") {
709 0         0 print FH FreezeThaw::freeze($self->{_cache_cache});
710             }
711 0         0 flock(FH, LOCK_UN);
712 0         0 close(FH);
713             }
714             else {
715 0         0 _debug("Error: Failed to seek to beginning of cache_file $self->{cache_file}: $!");
716             }
717             }
718             else {
719 0         0 _debug("Error: Failed to lock cache_file $self->{cache_file} exclusively: $!");
720             }
721             }
722             else {
723 0         0 _debug("Error: Failed to open cache_file $self->{cache_file} for writing: $!");
724             }
725              
726             }
727 1         56 return $arrayref;
728             }
729              
730             }
731              
732             ##################################################################################################################################
733             #
734             # Internal subroutines
735             #
736             sub _debug {
737 146     146   204 my $notice = shift;
738 146         190 $@ = $notice;
739 146 50       272 if ($DEBUG) {
740 0         0 print "$MYNAME DEBUG NOTE: $notice\n";
741 0         0 return 1;
742             }
743 146         196 return 0;
744             }
745              
746             sub _state2result {
747 1     1   2 my $stateref = shift;
748 1         3 my $arrayref = shift;
749 1         2 my ($temperature_fahrenheit, $temperature_celsius);
750 0         0 my ($dewpoint_fahrenheit, $dewpoint_celsius);
751 0         0 my ($visibility_miles, $visibility_kilometers);
752 0         0 my ($wind_direction, $wind_milesperhour, $wind_kilometersperhour);
753              
754             #
755             # Avoid some silly warnings of unitialized values
756             #
757 1         4 foreach (qw(content_PLACE content_UPDATED content_TEMPERATURE content_HUMIDITY content_DEW_POINT content_WIND content_PRESSURE content_CONDITIONS content_VISIBILITY content_CLOUDS content_SUNRISE content_SUNSET content_MOON_RISE content_MOON_SET content_MOON_PHASE)) {
758 15 50       44 exists($stateref->{$_}) or ($stateref->{$_} = "");
759             }
760              
761 1         9 $stateref->{"content_TEMPERATURE"} =~ s/\s//g;
762 1         14 ($temperature_celsius) = ($stateref->{"content_TEMPERATURE"} =~ /(-?([?[0-9]*\.?[0-9]+))°c/i);
763 1         7 ($temperature_fahrenheit) = ($stateref->{"content_TEMPERATURE"} =~ /(-?([?[0-9]*\.?[0-9]+))°f/i);
764 1 50 33     37 if (!length($temperature_celsius) && length($temperature_fahrenheit)) {
    50 33        
765 0         0 $temperature_celsius = ($temperature_fahrenheit - 32) / 1.8;
766             }
767             elsif (!length($temperature_fahrenheit) && length($temperature_celsius)) {
768 0         0 $temperature_fahrenheit = ($temperature_celsius * 1.8) + 32;
769             }
770              
771 1         6 $stateref->{"content_DEW_POINT"} =~ s/\s//g;
772 1         12 ($dewpoint_celsius) = ($stateref->{"content_DEW_POINT"} =~ /(-?([?[0-9]*\.?[0-9]+))°c/i);
773 1         7 ($dewpoint_fahrenheit) = ($stateref->{"content_DEW_POINT"} =~ /(-?([?[0-9]*\.?[0-9]+))°f/i);
774 1 50 33     14 if (!length($dewpoint_celsius) && length($dewpoint_fahrenheit)) {
    50 33        
775 0         0 $dewpoint_celsius = ($dewpoint_fahrenheit - 32) / 1.8;
776             }
777             elsif (!length($dewpoint_fahrenheit) && length($dewpoint_celsius)) {
778 0         0 $dewpoint_fahrenheit = ($dewpoint_celsius * 1.8) + 32;
779             }
780              
781 1         8 $stateref->{"content_VISIBILITY"} =~ s/\s//g;
782 1         6 ($visibility_miles) = ($stateref->{"content_VISIBILITY"} =~ /([0-9.]+)[^a-z0-9]*?m/i);
783 1         8 ($visibility_kilometers) = ($stateref->{"content_VISIBILITY"} =~ /([0-9.]+)[^a-z0-9]*?k/i);
784 1 50 33     13 if (!length($visibility_miles) && length($visibility_kilometers)) {
    50 33        
785 0         0 $visibility_miles = $visibility_kilometers * 0.621371192;
786             }
787             elsif (!length($visibility_kilometers) && length($visibility_miles)) {
788 0         0 $visibility_kilometers = $visibility_miles * 1.609344;
789             }
790              
791 1         5 $stateref->{"content_HUMIDITY"} =~ s/[^0-9]//g;
792              
793 1         7 ($wind_direction) = $stateref->{"content_WIND"} =~ /^([a-z -]+?)\s+(at|[0-9])/i;
794 1         8 $stateref->{"content_WIND"} =~ s/\s//g;
795 1         6 ($wind_milesperhour) = ($stateref->{"content_WIND"} =~ /([0-9.]+)[^a-z0-9]*?mp/i);
796 1         6 ($wind_kilometersperhour) = ($stateref->{"content_WIND"} =~ /([0-9.]+)[^a-z0-9]*?km/i);
797 1 50 33     14 if (!length($wind_milesperhour) && length($wind_kilometersperhour)) {
    50 33        
798 0         0 $wind_milesperhour = $wind_kilometersperhour * 0.621371192;
799             }
800             elsif (!length($wind_kilometersperhour) && length($wind_milesperhour)) {
801 0         0 $wind_kilometersperhour = $wind_milesperhour * 1.609344;
802             }
803              
804             push(
805 1         44 @$arrayref,
806             {
807             place => $stateref->{"content_PLACE"},
808             updated => $stateref->{"content_UPDATED"},
809             temperature_celsius => $temperature_celsius,
810             temperature_fahrenheit => $temperature_fahrenheit,
811             celsius => $temperature_celsius, # Legacy
812             fahrenheit => $temperature_fahrenheit, # Legacy
813             humidity => $stateref->{"content_HUMIDITY"},
814             dewpoint_celsius => $dewpoint_celsius,
815             dewpoint_fahrenheit => $dewpoint_fahrenheit,
816             wind_direction => $wind_direction,
817             wind_milesperhour => $wind_milesperhour,
818             wind_kilometersperhour => $wind_kilometersperhour,
819             pressure => $stateref->{"content_PRESSURE"},
820             conditions => $stateref->{"content_CONDITIONS"},
821             visibility_miles => $visibility_miles,
822             visibility_kilometers => $visibility_kilometers,
823             clouds => $stateref->{"content_CLOUDS"},
824             sunrise => $stateref->{"content_SUNRISE"},
825             sunset => $stateref->{"content_SUNSET"},
826             moonrise => $stateref->{"content_MOON_RISE"},
827             moonset => $stateref->{"content_MOON_SET"},
828             moonphase => $stateref->{"content_MOON_PHASE"},
829             }
830             );
831              
832             }
833              
834             #
835             # Leave me alone:
836             #
837             1;
838