00001 #! /usr/bin/perl
00002 # vim:ts=4:sw=4:ai:et:si:sts=4
00003
00004 use strict;
00005 use warnings;
00006
00007 use utf8;
00008 use encoding 'utf8';
00009 use LWP::UserAgent;
00010 use Getopt::Std;
00011 use URI::Escape;
00012 use XML::XPath;
00013 use XML::XPath::XMLParser;
00014 use POSIX qw(strftime);
00015 use File::Path;
00016
00017 our ($opt_v, $opt_t, $opt_T, $opt_l, $opt_u, $opt_d, $opt_D);
00018
00019 my $name = 'wunderground';
00020 my $version = 0.1;
00021 my $author = 'Gavin Hurlbut';
00022 my $email = 'gjhurlbu@gmail.com';
00023 my $updateTimeout = 15*60;
00024 my $retrieveTimeout = 30;
00025 my @types = ( '3dlocation', '6dlocation', 'cclocation', 'copyright',
00026 'date-0', 'date-1', 'date-2', 'date-3', 'date-4', 'date-5',
00027 'high-0', 'high-1', 'high-2', 'high-3', 'high-4', 'high-5',
00028 'low-0', 'low-1', 'low-2', 'low-3', 'low-4', 'low-5',
00029 'icon-0', 'icon-1', 'icon-2', 'icon-3', 'icon-4', 'icon-5',
00030 'observation_time', 'updatetime', 'station_id' );
00031 my $dir = "/tmp/wunderground";
00032 my $logdir = "/tmp/wunderground";
00033 my %images = ( "clear" => "fair.png", "cloudy" => "cloudy.png",
00034 "flurries" => "flurries.png", "fog" => "fog.png",
00035 "hazy" => "fog.png", "mostlycloudy" => "mcloudy.png",
00036 "mostlysunny" => "pcloudy.png", "partlycloudy" => "pcloudy.png",
00037 "partlysunny" => "mcloudy.png", "rain" => "showers.png",
00038 "sleet" => "rainsnow.png", "snow" => "flurries.png",
00039 "sunny" => "sunny.png", "tstorms" => "thunshowers.png",
00040 "unknown" => "unknown.png" );
00041
00042 binmode(STDOUT, ":utf8");
00043
00044 if (!-d $logdir) {
00045 mkpath( $logdir, {mode => 0755} );
00046 }
00047
00048 getopts('Tvtlu:d:');
00049
00050 if (defined $opt_v) {
00051 print "$name,$version,$author,$email\n";
00052 log_print( $logdir, "-v\n" );
00053 exit 0;
00054 }
00055
00056 if (defined $opt_T) {
00057 print "$updateTimeout,$retrieveTimeout\n";
00058 log_print( $logdir, "-t\n" );
00059 exit 0;
00060 }
00061
00062 if (defined $opt_d) {
00063 $dir = $opt_d;
00064 }
00065
00066 if (!-d $dir) {
00067 mkpath( $dir, {mode => 0755} );
00068 }
00069
00070 if (defined $opt_l) {
00071 my $search = uri_escape(shift);
00072 log_print( $logdir, "-l $search\n" );
00073 my $base_url =
00074 'http://api.wunderground.com/auto/wui/geo/GeoLookupXML/index.xml?query=';
00075
00076 my $xp = getCachedXML($base_url . $search, $dir, $search . ".html",
00077 $updateTimeout, $logdir);
00078
00079 # This can return two different types of responses. One where there is a
00080 # list of locations, and one where it's unique.
00081 my $nodeset = $xp->find('/locations/location');
00082 my $city;
00083 my $state;
00084 if( $nodeset->size == 0 ) {
00085 # Single location
00086 $city = $xp->getNodeText('/location/city');
00087 $state = $xp->getNodeText('/location/state');
00088 if( not defined $state ) {
00089 $state = "";
00090 } else {
00091 $state = ", $state";
00092 }
00093 print $city . "::$city$state, " .
00094 $xp->getNodeText('/location/country') . "\n";
00095 } else {
00096 # Multiple locations
00097 foreach my $node ($nodeset->get_nodelist) {
00098 my $type = $node->getAttribute('type');
00099 next unless $type eq "CITY" or $type eq "INTLCITY";
00100 $city = $xp->find("name", $node);
00101 print "$city" . "::$city\n";
00102 }
00103 }
00104
00105 exit 0;
00106 }
00107
00108 if (defined $opt_t) {
00109 foreach (@types) {print; print "\n";}
00110 exit 0;
00111 }
00112
00113 # we get here, we're doing an actual retrieval, everything must be defined
00114 my $rawloc = shift;
00115 my $loc = uri_escape($rawloc);
00116 if (!(defined $opt_u && defined $loc && !$loc eq "")) {
00117 die "Invalid usage";
00118 }
00119
00120 my %attrib;
00121 my $units = $opt_u;
00122 log_print( $logdir, "-u $units -d $dir $loc\n" );
00123
00124
00125 my $base_url =
00126 'http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=';
00127 my $file = $loc;
00128 $file =~ s/\
00129
00130 my $xp = getCachedXML($base_url . $loc, $dir, $file . ".xml",
00131 $updateTimeout, $logdir);
00132
00133 $attrib{"station_id"} = $rawloc;
00134
00135 my $nodeset;
00136 my $node;
00137
00138 $attrib{"cclocation"} = $rawloc;
00139 $attrib{"3dlocation"} = $rawloc;
00140 $attrib{"6dlocation"} = $rawloc;
00141
00142 $attrib{"copyright"} = "Weather data courtesy of Weather Underground, Inc.";
00143
00144 my $now = time;
00145 $attrib{"updatetime"} = format_date($now);
00146
00147 $attrib{"observation_time"} = $xp->getNodeText('/forecast/txt_forecast/date');
00148
00149 my @forecast;
00150 $nodeset = $xp->find('/forecast/simpleforecast/forecastday');
00151 foreach $node ($nodeset->get_nodelist) {
00152 my $hashref = {};
00153
00154 nodeToHash( $node, "", $hashref );
00155 push @forecast, $hashref;
00156 }
00157
00158 my $day = 0;
00159 my $time = 0;
00160 #foreach my $hashref (@forecast) {
00161 # print "---------------\n";
00162 # foreach my $key ( sort keys %$hashref ) {
00163 # print $key . "::" . $hashref->{$key} . "\n";
00164 # }
00165 #}
00166
00167 foreach my $hashref (@forecast) {
00168 my $fromtime = $hashref->{"forecastday::date::epoch"};
00169 if( $day < 6 ) {
00170 $attrib{"date-$day"} = format_date($fromtime);
00171 my $icon = lc $hashref->{"forecastday::icon"};
00172 $icon =~ s/^chance
00173 my $img = $images{$icon};
00174 if (not defined $img) {
00175 log_print( $dir, "Unknown image mapping: " .
00176 $hashref->{"forecastday::icon"} . "\n" );
00177 $img = $images{"unknown"};
00178 }
00179 $attrib{"icon-$day"} = $img;
00180 if ($units eq "SI") {
00181 $attrib{"high-$day"} = $hashref->{"forecastday::high::celsius"};
00182 $attrib{"low-$day"} = $hashref->{"forecastday::low::celsius"};
00183 } else {
00184 $attrib{"high-$day"} = $hashref->{"forecastday::high::fahrenheit"};
00185 $attrib{"low-$day"} = $hashref->{"forecastday::low::fahrenheit"};
00186 }
00187 $day++;
00188 }
00189 }
00190
00191 for my $attr ( sort keys %attrib ) {
00192 print $attr . "::" . $attrib{$attr} . "\n";
00193 }
00194 exit 0;
00195
00196 #
00197 # Subroutines
00198 #
00199 sub nodeToHash {
00200 my ($node, $prefix, $hashref) = @_;
00201
00202 my $nodename = $node->getName;
00203 my @subnodelist = $node->getChildNodes;
00204
00205 if ( not defined $prefix or $prefix eq "" ) {
00206 $prefix = $nodename;
00207 } elsif ( defined $nodename ) {
00208 $prefix = $prefix . "::" . $nodename;
00209 }
00210
00211 foreach my $attr ( $node->getAttributes ) {
00212 $prefix .= "::".$attr->getName."=".$attr->getData;
00213 }
00214
00215 if ( $#subnodelist == 0 ) {
00216 $hashref->{$prefix} = $node->string_value;
00217 } else {
00218 foreach my $subnode ( @subnodelist ) {
00219 nodeToHash( $subnode, $prefix, $hashref );
00220 }
00221 }
00222 }
00223
00224 sub getCachedXML {
00225 my ($url, $dir, $file, $timeout, $logdir) = @_;
00226
00227 my $cachefile = "$dir/$file";
00228 my $xp;
00229
00230 my $now = time();
00231
00232 if( (-e $cachefile) and ((stat($cachefile))[9] >= ($now - $timeout)) ) {
00233 # File cache is still recent.
00234 log_print( $logdir, "cached in $cachefile\n" );
00235 } else {
00236 log_print( $logdir, "$url\ncaching to $cachefile\n" );
00237 my $ua = LWP::UserAgent->new;
00238 $ua->timeout(30);
00239 $ua->env_proxy;
00240 $ua->default_header('Accept-Language' => "en");
00241
00242 my $response = $ua->get($url);
00243 if ( !$response->is_success ) {
00244 die $response->status_line;
00245 }
00246
00247 open OF, ">$cachefile" or die "Can't open $cachefile: $!\n";
00248 print OF $response->content;
00249 close OF;
00250 }
00251
00252 $xp = XML::XPath->new(filename => $cachefile);
00253
00254 return $xp;
00255 }
00256
00257 sub format_date {
00258 my ($time) = @_;
00259
00260 return strftime '%a %b %e, %Y %H:%M:%S', localtime($time);
00261 }
00262
00263 sub log_print {
00264 return if not defined $opt_D;
00265 my $dir = shift;
00266
00267 open OF, ">>$dir/wunderground.log";
00268 print OF @_;
00269 close OF;
00270 }