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 JSON;
00015 use DateTime::Format::ISO8601;
00016 use POSIX qw(strftime);
00017 use File::Path;
00018
00019 our ($opt_v, $opt_t, $opt_T, $opt_l, $opt_u, $opt_d, $opt_D);
00020
00021 my $name = 'yrno-XML';
00022 my $version = 0.2;
00023 my $author = 'Gavin Hurlbut';
00024 my $email = 'gjhurlbu@gmail.com';
00025 my $updateTimeout = 15*60;
00026 my $retrieveTimeout = 30;
00027 my @types = ( '3dlocation',
00028 '6dlocation', 'altitude', 'cclocation', 'copyright', 'date-0',
00029 'date-1', 'date-2', 'date-3', 'date-4', 'date-5', 'geobaseid',
00030 'high-0', 'high-1', 'high-2', 'high-3', 'high-4', 'high-5',
00031 'low-0', 'low-1', 'low-2', 'low-3', 'low-4', 'low-5',
00032 'icon-0', 'icon-1', 'icon-2', 'icon-3', 'icon-4', 'icon-5',
00033 'latitude', 'longitude', 'observation_time',
00034 '18hrlocation',
00035 '18icon-0', '18icon-1', '18icon-2',
00036 '18icon-3', '18icon-4', '18icon-5',
00037 'temp-0', 'temp-1', 'temp-2', 'temp-3', 'temp-4', 'temp-5',
00038 'time-0', 'time-1', 'time-2', 'time-3', 'time-4', 'time-5',
00039 'pop-0', 'pop-1', 'pop-2', 'pop-3', 'pop-4', 'pop-5',
00040 'updatetime', 'station_id' );
00041 my $dir = "/tmp/yrnoxml";
00042 my $logdir = "/tmp/yrnoxml";
00043 my %images = ( "partly cloudy" => "pcloudy.png", "cloudy" => "cloudy.png",
00044 "sleet" => "rainsnow.png", "fair" => "fair.png",
00045 "snow" => "flurries.png", "rain" => "showers.png",
00046 "sunny" => "sunny.png", "fog" => "fog.png",
00047 "mostly cloudy" => "mcloudy.png",
00048 "rain showers" => "lshowers.png", "heavy rain" => "showers.png",
00049 "thunder showers" => "thunshowers.png",
00050 "unknown" => "unknown.png" );
00051
00052 binmode(STDOUT, ":utf8");
00053
00054 if (!-d $logdir) {
00055 mkpath( $logdir, {mode => 0755} );
00056 }
00057
00058 getopts('Tvtlu:d:D');
00059
00060 if (defined $opt_v) {
00061 print "$name,$version,$author,$email\n";
00062 log_print( $logdir, "-v\n" );
00063 exit 0;
00064 }
00065
00066 if (defined $opt_T) {
00067 print "$updateTimeout,$retrieveTimeout\n";
00068 log_print( $logdir, "-t\n" );
00069 exit 0;
00070 }
00071
00072 if (defined $opt_d) {
00073 $dir = $opt_d;
00074 }
00075
00076 if (!-d $dir) {
00077 mkpath( $dir, {mode => 0755} );
00078 }
00079
00080 if (defined $opt_l) {
00081 my $search = uri_escape(shift);
00082 log_print( $logdir, "-l $search\n" );
00083 my $base_url = 'http://www.yr.no/_/websvc/jsonforslagsboks.aspx?'
00084 . 's1t=&s1i=&s2t=&s2i=&s=';
00085
00086 my $response = getCachedJSON($base_url . $search, $dir, $search . ".json",
00087 $updateTimeout, $logdir);
00088
00089 my @cities = @{$$response[1]};
00090 if (@cities) {
00091 foreach my $city (@cities) {
00092 my ($cityName, $url, $location, $country) = @{$city};
00093
00094 $url =~ s/^\/place\
00095 $url =~ s/\/$
00096
00097 print $url . "::" . "$cityName, $location, $country\n";
00098 }
00099 }
00100
00101 exit 0;
00102 }
00103
00104 if (defined $opt_t) {
00105 foreach (@types) {print; print "\n";}
00106 exit 0;
00107 }
00108
00109 # we get here, we're doing an actual retrieval, everything must be defined
00110 my $loc = shift;
00111 if (!(defined $opt_u && defined $loc && !$loc eq "")) {
00112 die "Invalid usage";
00113 }
00114
00115 my %attrib;
00116 my $units = $opt_u;
00117 log_print( $logdir, "-u $units -d $dir $loc\n" );
00118
00119
00120 my $base_url = 'http://www.yr.no/place/';
00121 my $file = $loc;
00122 $file =~ s/\
00123
00124 my $xp = getCachedXML($base_url . $loc . "/forecast.xml", $dir, $file . ".xml",
00125 $updateTimeout, $logdir);
00126
00127 $attrib{"station_id"} = $loc;
00128
00129 my $nodeset;
00130 my $node;
00131
00132 $name = $xp->getNodeText('/weatherdata/location/name');
00133 $name .= ", " . $xp->getNodeText('/weatherdata/location/country');
00134
00135 $attrib{"cclocation"} = $name;
00136 $attrib{"3dlocation"} = $name;
00137 $attrib{"6dlocation"} = $name;
00138 $attrib{"18hrlocation"} = $name;
00139
00140 $nodeset = $xp->find('/weatherdata/location/location');
00141 foreach $node ($nodeset->get_nodelist) {
00142 $attrib{"altitude"} = convert_alt($node->getAttribute("altitude"), $units);
00143 $attrib{"latitude"} = $node->getAttribute("latitude");
00144 $attrib{"longitude"} = $node->getAttribute("longitude");
00145 $attrib{"geobaseid"} = $node->getAttribute("geobaseid");
00146 }
00147
00148 $nodeset = $xp->find('/weatherdata/credit/link');
00149 foreach $node ($nodeset->get_nodelist) {
00150 $attrib{"copyright"} = $node->getAttribute("text");
00151 }
00152
00153 my $tzoffset;
00154 $nodeset = $xp->find('/weatherdata/location/timezone');
00155 foreach $node ($nodeset->get_nodelist) {
00156 $tzoffset = $node->getAttribute("utcoffsetMinutes");
00157 }
00158 $tzoffset *= 60;
00159 my $now = time;
00160 $attrib{"updatetime"} = format_date($now);
00161
00162 $attrib{"observation_time"} = format_date(
00163 parse_date($xp->getNodeText('/weatherdata/meta/lastupdate'), $tzoffset));
00164
00165 my $lastperiod = undef;
00166 my @forecast;
00167 $nodeset = $xp->find('/weatherdata/forecast/tabular/time');
00168 foreach $node ($nodeset->get_nodelist) {
00169 my $hashref = {};
00170
00171 nodeToHash( $node, $hashref );
00172 push @forecast, $hashref;
00173 $lastperiod = $hashref->{"time::period"};
00174 }
00175
00176 my $day = 0;
00177 my $time = 0;
00178 foreach my $hashref (@forecast) {
00179 # foreach my $key ( sort keys %$hashref ) {
00180 # print $key . "::" . $hashref->{$key} . "\n";
00181 # }
00182 my $fromtime = parse_date($hashref->{"time::from"}, $tzoffset);
00183 if( $day < 6 and $hashref->{"time::period"} == $lastperiod ) {
00184 $attrib{"date-$day"} = format_date($fromtime);
00185 my $img = $images{lc $hashref->{"symbol::name"}};
00186 if (not defined $img) {
00187 log_print( $dir, "Unknown image mapping: " .
00188 $hashref->{"symbol::name"} . "\n" );
00189 $img = $images{"unknown"};
00190 }
00191 $attrib{"icon-$day"} = $img;
00192 $attrib{"high-$day"} = convert_temp( $hashref->{"temperature::value"},
00193 $units );
00194 $attrib{"low-$day"} = "N/A";
00195 $day++;
00196 }
00197 if ($time < 6 and $fromtime > $now) {
00198 $attrib{"time-$time"} = format_date($fromtime);
00199 my $img = $images{lc $hashref->{"symbol::name"}};
00200 if (not defined $img) {
00201 log_print( $dir, "Unknown image mapping: " .
00202 $hashref->{"symbol::name"} . "\n" );
00203 $img = $images{"unknown"};
00204 }
00205 $attrib{"18icon-$time"} = $img;
00206 $attrib{"temp-$time"} = convert_temp( $hashref->{"temperature::value"},
00207 $units );
00208 $attrib{"pop-$time"} = "N/A";
00209 $time++;
00210 }
00211 }
00212
00213 for my $attr ( sort keys %attrib ) {
00214 print $attr . "::" . $attrib{$attr} . "\n";
00215 }
00216 exit 0;
00217
00218 #
00219 # Subroutines
00220 #
00221 sub nodeToHash {
00222 my ($node, $hashref) = @_;
00223
00224 my $nodename = $node->getName;
00225
00226 foreach my $attr ( $node->getAttributes ) {
00227 $hashref->{$nodename."::".$attr->getName} = $attr->getData;
00228 }
00229
00230 foreach my $subnode ( $node->getChildNodes ) {
00231 nodeToHash( $subnode, $hashref );
00232 }
00233 }
00234
00235 sub getCachedXML {
00236 my ($url, $dir, $file, $timeout, $logdir) = @_;
00237
00238 my $cachefile = "$dir/$file";
00239 my $xp;
00240
00241 my $now = time();
00242
00243 if( (-e $cachefile) and ((stat($cachefile))[9] >= ($now - $timeout)) ) {
00244 # File cache is still recent.
00245 log_print( $logdir, "cached in $cachefile\n" );
00246 } else {
00247 log_print( $logdir, "$url\ncaching to $cachefile\n" );
00248 my $ua = LWP::UserAgent->new;
00249 $ua->timeout(30);
00250 $ua->env_proxy;
00251 $ua->default_header('Accept-Language' => "en");
00252
00253 my $response = $ua->get($url);
00254 if ( !$response->is_success ) {
00255 die $response->status_line;
00256 }
00257
00258 open OF, ">:utf8", $cachefile or die "Can't open $cachefile: $!\n";
00259 print OF $response->content;
00260 close OF;
00261 }
00262
00263 $xp = XML::XPath->new(filename => $cachefile);
00264
00265 return $xp;
00266 }
00267
00268 sub getCachedJSON {
00269 my ($url, $dir, $file, $timeout, $logdir) = @_;
00270
00271 my $cachefile = "$dir/$file";
00272 my $xp;
00273
00274 my $now = time();
00275
00276 if( (-e $cachefile) and ((stat($cachefile))[9] >= ($now - $timeout)) ) {
00277 # File cache is still recent.
00278 log_print( $logdir, "cached in $cachefile\n" );
00279 } else {
00280 log_print( $logdir, "$url\ncaching to $cachefile\n" );
00281 my $accept = "application/json, text/javascript, */*; q=0.01";
00282 my $ua = LWP::UserAgent->new;
00283 $ua->timeout(30);
00284 $ua->env_proxy;
00285 $ua->default_header('Accept' => $accept);
00286 $ua->default_header('Accept-Language' => "en");
00287
00288 my $response = $ua->get($url);
00289 if ( !$response->is_success ) {
00290 die $response->status_line;
00291 }
00292
00293 open OF, ">:utf8", $cachefile or die "Can't open $cachefile: $!\n";
00294 print OF $response->content;
00295 close OF;
00296 }
00297
00298 open IF, "<:utf8", $cachefile or die "Can't open $cachefile: $!\n";
00299 my $content = do { local $/; <IF>; };
00300 close IF;
00301
00302 return decode_json($content);
00303 }
00304
00305 sub convert_temp {
00306 my ( $degC, $units ) = @_;
00307 my $deg;
00308
00309 if( $units ne "SI" ) {
00310 $deg = int(($degC * 1.8) + 32.5);
00311 } else {
00312 $deg = $degC;
00313 }
00314 return $deg;
00315 }
00316
00317 sub parse_date {
00318 my ( $date, $tzoffset ) = @_;
00319 my $time = DateTime::Format::ISO8601->parse_datetime( $date );
00320
00321 $time = $time->epoch - $tzoffset;
00322 return $time;
00323 }
00324
00325 sub format_date {
00326 my ($time) = @_;
00327
00328 return strftime '%a %b %e, %Y %H:%M:%S', localtime($time);
00329 }
00330
00331 sub convert_alt {
00332 my ( $altm, $units ) = @_;
00333 my $alt;
00334
00335 if( $units ne "SI" ) {
00336 $alt = int(($altm * (100 / 2.54 / 12)) + 0.5);
00337 } else {
00338 $alt = $altm;
00339 }
00340 return $alt;
00341 }
00342
00343 sub log_print {
00344 return if not defined $opt_D;
00345 my $dir = shift;
00346
00347 open OF, ">>$dir/yrnoxml.log";
00348 print OF @_;
00349 close OF;
00350 }