MythTV  master
yrnoxml.pl
Go to the documentation of this file.
1 #! /usr/bin/perl
2 # vim:ts=4:sw=4:ai:et:si:sts=4
3 
4 use strict;
5 use warnings;
6 
7 use utf8;
8 
9 use LWP::UserAgent;
10 use Getopt::Std;
11 use URI::Escape;
12 use XML::XPath;
13 use XML::XPath::XMLParser;
14 use JSON;
15 use DateTime::Format::ISO8601;
16 use POSIX qw(strftime);
17 use File::Path;
18 
19 our ($opt_v, $opt_t, $opt_T, $opt_l, $opt_u, $opt_d, $opt_D);
20 
21 my $name = 'yrno-XML';
22 my $version = 0.4;
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',
34  '18hrlocation',
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" );
51 
52 binmode(STDOUT, ":utf8");
53 
54 if (!-d $logdir) {
55  mkpath( $logdir, {mode => 0755} );
56 }
57 
58 getopts('Tvtlu:d:D');
59 
60 if (defined $opt_v) {
61  print "$name,$version,$author,$email\n";
62  log_print( $logdir, "-v\n" );
63  exit 0;
64 }
65 
66 if (defined $opt_T) {
67  print "$updateTimeout,$retrieveTimeout\n";
68  log_print( $logdir, "-t\n" );
69  exit 0;
70 }
71 
72 if (defined $opt_d) {
73  $dir = $opt_d;
74 }
75 
76 if (!-d $dir) {
77  mkpath( $dir, {mode => 0755} );
78 }
79 
80 if (defined $opt_l) {
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=';
85 
86  my $response = getCachedJSON($base_url . $search, $dir, $search . ".json",
87  $updateTimeout, $logdir);
88 
89  my @cities = @{$$response[1]};
90  if (@cities) {
91  foreach my $city (@cities) {
92  my ($cityName, $url, $location, $country) = @{$city};
93 
94  $url =~ s/^\/place\///;
95  $url =~ s/\/$//;
96 
97  print $url . "::" . "$cityName, $location, $country\n";
98  }
99  }
100 
101  exit 0;
102 }
103 
104 if (defined $opt_t) {
105  foreach (@types) {print; print "\n";}
106  exit 0;
107 }
108 
109 # we get here, we're doing an actual retrieval, everything must be defined
110 my $loc = shift;
111 if (!(defined $opt_u && defined $loc && !$loc eq "")) {
112  die "Invalid usage";
113 }
114 
115 my %attrib;
116 my $units = $opt_u;
117 log_print( $logdir, "-u $units -d $dir $loc\n" );
118 
119 
120 my $base_url = 'http://www.yr.no/place/';
121 my $file = $loc;
122 $file =~ s/\//-/g;
123 
124 my $xp = getCachedXML($base_url . $loc . "/forecast.xml", $dir, $file . ".xml",
125  $updateTimeout, $logdir);
126 
127 $attrib{"station_id"} = $loc;
128 
129 my $nodeset;
130 my $node;
131 
132 $name = $xp->getNodeText('/weatherdata/location/name');
133 $name .= ", " . $xp->getNodeText('/weatherdata/location/country');
134 
135 $attrib{"cclocation"} = $name;
136 $attrib{"3dlocation"} = $name;
137 $attrib{"6dlocation"} = $name;
138 $attrib{"18hrlocation"} = $name;
139 
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");
146 }
147 
148 $nodeset = $xp->find('/weatherdata/credit/link');
149 foreach $node ($nodeset->get_nodelist) {
150  $attrib{"copyright"} = $node->getAttribute("text");
151 }
152 $attrib{"copyrightlogo"} = "none";
153 
154 my $tzoffset;
155 $nodeset = $xp->find('/weatherdata/location/timezone');
156 foreach $node ($nodeset->get_nodelist) {
157  $tzoffset = $node->getAttribute("utcoffsetMinutes");
158 }
159 $tzoffset *= 60;
160 my $now = time;
161 $attrib{"updatetime"} = format_date($now);
162 
163 $attrib{"observation_time"} = format_date(
164  parse_date($xp->getNodeText('/weatherdata/meta/lastupdate'), $tzoffset));
165 
166 my $lastperiod = undef;
167 my @forecast;
168 $nodeset = $xp->find('/weatherdata/forecast/tabular/time');
169 foreach $node ($nodeset->get_nodelist) {
170  my $hashref = {};
171 
172  nodeToHash( $node, $hashref );
173  push @forecast, $hashref;
174  $lastperiod = $hashref->{"time::period"};
175 }
176 
177 my $day = 0;
178 my $time = 0;
179 foreach my $hashref (@forecast) {
180 # foreach my $key ( sort keys %$hashref ) {
181 # print $key . "::" . $hashref->{$key} . "\n";
182 # }
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"};
191  }
192  $attrib{"icon-$day"} = $img;
193  $attrib{"high-$day"} = convert_temp( $hashref->{"temperature::value"},
194  $units );
195  $attrib{"low-$day"} = "N/A";
196  $day++;
197  }
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"};
205  }
206  $attrib{"18icon-$time"} = $img;
207  $attrib{"temp-$time"} = convert_temp( $hashref->{"temperature::value"},
208  $units );
209  $attrib{"pop-$time"} = "N/A";
210  $time++;
211  }
212 }
213 
214 for my $attr ( sort keys %attrib ) {
215  print $attr . "::" . $attrib{$attr} . "\n";
216 }
217 exit 0;
218 
219 #
220 # Subroutines
221 #
222 sub nodeToHash {
223  my ($node, $hashref) = @_;
224 
225  my $nodename = $node->getName;
226 
227  foreach my $attr ( $node->getAttributes ) {
228  $hashref->{$nodename."::".$attr->getName} = $attr->getData;
229  }
230 
231  foreach my $subnode ( $node->getChildNodes ) {
232  nodeToHash( $subnode, $hashref );
233  }
234 }
235 
236 sub getCachedXML {
237  my ($url, $dir, $file, $timeout, $logdir) = @_;
238 
239  my $cachefile = "$dir/$file";
240  my $xp;
241 
242  my $now = time();
243 
244  if( (-e $cachefile) and ((stat($cachefile))[9] >= ($now - $timeout)) ) {
245  # File cache is still recent.
246  log_print( $logdir, "cached in $cachefile\n" );
247  } else {
248  log_print( $logdir, "$url\ncaching to $cachefile\n" );
249  my $ua = LWP::UserAgent->new;
250  $ua->timeout(30);
251  $ua->env_proxy;
252  $ua->default_header('Accept-Language' => "en");
253 
254  my $response = $ua->get($url);
255  if ( !$response->is_success ) {
256  die $response->status_line;
257  }
258 
259  open OF, ">:utf8", $cachefile or die "Can't open $cachefile: $!\n";
260  print OF $response->content;
261  close OF;
262  }
263 
264  $xp = XML::XPath->new(filename => $cachefile);
265 
266  return $xp;
267 }
268 
269 sub getCachedJSON {
270  my ($url, $dir, $file, $timeout, $logdir) = @_;
271 
272  my $cachefile = "$dir/$file";
273  my $xp;
274 
275  my $now = time();
276 
277  if( (-e $cachefile) and ((stat($cachefile))[9] >= ($now - $timeout)) ) {
278  # File cache is still recent.
279  log_print( $logdir, "cached in $cachefile\n" );
280  } else {
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;
284  $ua->timeout(30);
285  $ua->env_proxy;
286  $ua->default_header('Accept' => $accept);
287  $ua->default_header('Accept-Language' => "en");
288 
289  my $response = $ua->get($url);
290  if ( !$response->is_success ) {
291  die $response->status_line;
292  }
293 
294  open OF, ">:utf8", $cachefile or die "Can't open $cachefile: $!\n";
295  print OF $response->content;
296  close OF;
297  }
298 
299  open IF, "<:utf8", $cachefile or die "Can't open $cachefile: $!\n";
300  my $content = do { local $/; <IF>; };
301  close IF;
302 
303  return decode_json($content);
304 }
305 
306 sub convert_temp {
307  my ( $degC, $units ) = @_;
308  my $deg;
309 
310  if( $units ne "SI" ) {
311  $deg = int(($degC * 1.8) + 32.5);
312  } else {
313  $deg = $degC;
314  }
315  return $deg;
316 }
317 
318 sub parse_date {
319  my ( $date, $tzoffset ) = @_;
320  my $time = DateTime::Format::ISO8601->parse_datetime( $date );
321 
322  $time = $time->epoch - $tzoffset;
323  return $time;
324 }
325 
326 sub format_date {
327  my ($time) = @_;
328 
329  return strftime '%a %b %e, %Y %H:%M:%S', localtime($time);
330 }
331 
332 sub convert_alt {
333  my ( $altm, $units ) = @_;
334  my $alt;
335 
336  if( $units ne "SI" ) {
337  $alt = int(($altm * (100 / 2.54 / 12)) + 0.5);
338  } else {
339  $alt = $altm;
340  }
341  return $alt;
342 }
343 
344 sub log_print {
345  return if not defined $opt_D;
346  my $dir = shift;
347 
348  open OF, ">>$dir/yrnoxml.log";
349  print OF @_;
350  close OF;
351 }