2 # vim:ts=4:sw=4:ai:et:si:sts=4
13 use XML::XPath::XMLParser;
15 use DateTime::Format::ISO8601;
16 use POSIX qw(strftime);
19 our ($opt_v, $opt_t, $opt_T, $opt_l, $opt_u, $opt_d, $opt_D);
21 my $name = 'yrno-XML';
23 my $author = 'Gavin Hurlbut';
24 my $email = 'gjhurlbu@gmail.com';
25 my $updateTimeout = 15*60;
26 my $retrieveTimeout = 30;
27 my @types = ( '3dlocation',
28 '6dlocation', 'altitude', 'cclocation', 'copyright', 'date-0',
29 'date-1', 'date-2', 'date-3', 'date-4', 'date-5', 'geobaseid',
30 'high-0', 'high-1', 'high-2', 'high-3', 'high-4', 'high-5',
31 'low-0', 'low-1', 'low-2', 'low-3', 'low-4', 'low-5',
32 'icon-0', 'icon-1', 'icon-2', 'icon-3', 'icon-4', 'icon-5',
33 'latitude', 'longitude', 'observation_time',
35 '18icon-0', '18icon-1', '18icon-2',
36 '18icon-3', '18icon-4', '18icon-5',
37 'temp-0', 'temp-1', 'temp-2', 'temp-3', 'temp-4', 'temp-5',
38 'time-0', 'time-1', 'time-2', 'time-3', 'time-4', 'time-5',
39 'pop-0', 'pop-1', 'pop-2', 'pop-3', 'pop-4', 'pop-5',
40 'updatetime', 'station_id', 'copyrightlogo' );
41 my $dir = "/tmp/yrnoxml";
42 my $logdir = "/tmp/yrnoxml";
43 my %images = ( "partly cloudy" => "pcloudy.png", "cloudy" => "cloudy.png",
44 "sleet" => "rainsnow.png", "fair" => "fair.png",
45 "snow" => "flurries.png", "rain" => "showers.png",
46 "sunny" => "sunny.png", "fog" => "fog.png",
47 "mostly cloudy" => "mcloudy.png",
48 "rain showers" => "lshowers.png", "heavy rain" => "showers.png",
49 "thunder showers" => "thunshowers.png",
50 "unknown" => "unknown.png" );
52 binmode(STDOUT, ":utf8");
55 mkpath( $logdir, {mode => 0755} );
61 print "$name,$version,$author,$email\n";
62 log_print( $logdir, "-v\n" );
67 print "$updateTimeout,$retrieveTimeout\n";
68 log_print( $logdir, "-t\n" );
77 mkpath( $dir, {mode => 0755} );
81 my $search = uri_escape(shift);
82 log_print( $logdir, "-l $search\n" );
83 my $base_url = 'http://www.yr.no/_/websvc/jsonforslagsboks.aspx?'
84 . 's1t=&s1i=&s2t=&s2i=&s=';
86 my $response = getCachedJSON($base_url . $search, $dir, $search . ".json",
87 $updateTimeout, $logdir);
89 my @cities = @{$$response[1]};
91 foreach my $city (@cities) {
92 my ($cityName, $url, $location, $country) = @{$city};
94 $url =~ s/^\/place\///;
97 print $url . "::" . "$cityName, $location, $country\n";
104 if (defined $opt_t) {
105 foreach (@types) {print; print "\n";}
109 # we get here, we're doing an actual retrieval, everything must be defined
111 if (!(defined $opt_u && defined $loc && !$loc eq "")) {
117 log_print( $logdir, "-u $units -d $dir $loc\n" );
120 my $base_url = 'http://www.yr.no/place/';
124 my $xp = getCachedXML($base_url . $loc . "/forecast.xml", $dir, $file . ".xml",
125 $updateTimeout, $logdir);
127 $attrib{"station_id"} = $loc;
132 $name = $xp->getNodeText('/weatherdata/location/name');
133 $name .= ", " . $xp->getNodeText('/weatherdata/location/country');
135 $attrib{"cclocation"} = $name;
136 $attrib{"3dlocation"} = $name;
137 $attrib{"6dlocation"} = $name;
138 $attrib{"18hrlocation"} = $name;
140 $nodeset = $xp->find('/weatherdata/location/location');
141 foreach $node ($nodeset->get_nodelist) {
142 $attrib{"altitude"} = convert_alt($node->getAttribute("altitude"), $units);
143 $attrib{"latitude"} = $node->getAttribute("latitude");
144 $attrib{"longitude"} = $node->getAttribute("longitude");
145 $attrib{"geobaseid"} = $node->getAttribute("geobaseid");
148 $nodeset = $xp->find('/weatherdata/credit/link');
149 foreach $node ($nodeset->get_nodelist) {
150 $attrib{"copyright"} = $node->getAttribute("text");
152 $attrib{"copyrightlogo"} = "none";
155 $nodeset = $xp->find('/weatherdata/location/timezone');
156 foreach $node ($nodeset->get_nodelist) {
157 $tzoffset = $node->getAttribute("utcoffsetMinutes");
161 $attrib{"updatetime"} = format_date($now);
163 $attrib{"observation_time"} = format_date(
164 parse_date($xp->getNodeText('/weatherdata/meta/lastupdate'), $tzoffset));
166 my $lastperiod = undef;
168 $nodeset = $xp->find('/weatherdata/forecast/tabular/time');
169 foreach $node ($nodeset->get_nodelist) {
172 nodeToHash( $node, $hashref );
173 push @forecast, $hashref;
174 $lastperiod = $hashref->{"time::period"};
179 foreach my $hashref (@forecast) {
180 # foreach my $key ( sort keys %$hashref ) {
181 # print $key . "::" . $hashref->{$key} . "\n";
183 my $fromtime = parse_date($hashref->{"time::from"}, $tzoffset);
184 if( $day < 6 and $hashref->{"time::period"} == $lastperiod ) {
185 $attrib{"date-$day"} = format_date($fromtime);
186 my $img = $images{lc $hashref->{"symbol::name"}};
187 if (not defined $img) {
188 log_print( $dir, "Unknown image mapping: " .
189 $hashref->{"symbol::name"} . "\n" );
190 $img = $images{"unknown"};
192 $attrib{"icon-$day"} = $img;
193 $attrib{"high-$day"} = convert_temp( $hashref->{"temperature::value"},
195 $attrib{"low-$day"} = "N/A";
198 if ($time < 6 and $fromtime > $now) {
199 $attrib{"time-$time"} = format_date($fromtime);
200 my $img = $images{lc $hashref->{"symbol::name"}};
201 if (not defined $img) {
202 log_print( $dir, "Unknown image mapping: " .
203 $hashref->{"symbol::name"} . "\n" );
204 $img = $images{"unknown"};
206 $attrib{"18icon-$time"} = $img;
207 $attrib{"temp-$time"} = convert_temp( $hashref->{"temperature::value"},
209 $attrib{"pop-$time"} = "N/A";
214 for my $attr ( sort keys %attrib ) {
215 print $attr . "::" . $attrib{$attr} . "\n";
223 my ($node, $hashref) = @_;
225 my $nodename = $node->getName;
227 foreach my $attr ( $node->getAttributes ) {
228 $hashref->{$nodename."::".$attr->getName} = $attr->getData;
231 foreach my $subnode ( $node->getChildNodes ) {
232 nodeToHash( $subnode, $hashref );
237 my ($url, $dir, $file, $timeout, $logdir) = @_;
239 my $cachefile = "$dir/$file";
244 if( (-e $cachefile) and ((stat($cachefile))[9] >= ($now - $timeout)) ) {
245 # File cache is still recent.
246 log_print( $logdir, "cached in $cachefile\n" );
248 log_print( $logdir, "$url\ncaching to $cachefile\n" );
249 my $ua = LWP::UserAgent->new;
252 $ua->default_header('Accept-Language' => "en");
254 my $response = $ua->get($url);
255 if ( !$response->is_success ) {
256 die $response->status_line;
259 open OF, ">:utf8", $cachefile or die "Can't open $cachefile: $!\n";
260 print OF $response->content;
264 $xp = XML::XPath->new(filename => $cachefile);
270 my ($url, $dir, $file, $timeout, $logdir) = @_;
272 my $cachefile = "$dir/$file";
277 if( (-e $cachefile) and ((stat($cachefile))[9] >= ($now - $timeout)) ) {
278 # File cache is still recent.
279 log_print( $logdir, "cached in $cachefile\n" );
281 log_print( $logdir, "$url\ncaching to $cachefile\n" );
282 my $accept = "application/json, text/javascript, */*; q=0.01";
283 my $ua = LWP::UserAgent->new;
286 $ua->default_header('Accept' => $accept);
287 $ua->default_header('Accept-Language' => "en");
289 my $response = $ua->get($url);
290 if ( !$response->is_success ) {
291 die $response->status_line;
294 open OF, ">:utf8", $cachefile or die "Can't open $cachefile: $!\n";
295 print OF $response->content;
299 open IF, "<:utf8", $cachefile or die "Can't open $cachefile: $!\n";
300 my $content = do { local $/; <IF>; };
303 return decode_json($content);
307 my ( $degC, $units ) = @_;
310 if( $units ne "SI" ) {
311 $deg = int(($degC * 1.8) + 32.5);
319 my ( $date, $tzoffset ) = @_;
320 my $time = DateTime::Format::ISO8601->parse_datetime( $date );
322 $time = $time->epoch - $tzoffset;
329 return strftime '%a %b %e, %Y %H:%M:%S', localtime($time);
333 my ( $altm, $units ) = @_;
336 if( $units ne "SI" ) {
337 $alt = int(($altm * (100 / 2.54 / 12)) + 0.5);
345 return if not defined $opt_D;
348 open OF, ">>$dir/yrnoxml.log";