| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Weather::API::Base; |
|
2
|
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
694335
|
use 5.008; |
|
|
3
|
|
|
|
|
21
|
|
|
4
|
3
|
|
|
3
|
|
17
|
use strict; |
|
|
3
|
|
|
|
|
15
|
|
|
|
3
|
|
|
|
|
99
|
|
|
5
|
3
|
|
|
3
|
|
15
|
use warnings; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
154
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
3
|
|
|
3
|
|
16
|
use Carp; |
|
|
3
|
|
|
|
|
8
|
|
|
|
3
|
|
|
|
|
262
|
|
|
8
|
3
|
|
|
3
|
|
1647
|
use LWP::UserAgent; |
|
|
3
|
|
|
|
|
136827
|
|
|
|
3
|
|
|
|
|
129
|
|
|
9
|
3
|
|
|
3
|
|
23
|
use Time::Local; |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
232
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
3
|
|
|
3
|
|
23
|
use Exporter 'import'; |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
7157
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our @EXPORT_OK = qw(ts_to_date ts_to_iso_date datetime_to_ts convert_units mon_to_num num_to_mon); |
|
14
|
|
|
|
|
|
|
our %EXPORT_TAGS = (all => \@EXPORT_OK); |
|
15
|
|
|
|
|
|
|
our @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 NAME |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
Weather::API::Base - Base/util module for Weather API clients |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=cut |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
our $VERSION = '0.4'; |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
### Using Helper Functions |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Weather::API::Base qw(:all); |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# Get time in YYYY-MM-DD HH:mm:ss format, local time zone |
|
32
|
|
|
|
|
|
|
my $datetime = ts_to_date(time()); |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# Convert a date to unix timestamp |
|
35
|
|
|
|
|
|
|
my $ts = datetime_to_ts('2024-01-12 13:46:40'); |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Convert 30 degrees Celsius to Fahrenheit |
|
38
|
|
|
|
|
|
|
my $result = convert_units('C', 'F', 30); |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
### Building a Weather API client |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
use parent 'Weather::API::Base'; |
|
44
|
|
|
|
|
|
|
use Weather::API::Base qw(:all); |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Constructor |
|
47
|
|
|
|
|
|
|
sub new { |
|
48
|
|
|
|
|
|
|
my ($class, %args) = @_; |
|
49
|
|
|
|
|
|
|
return $class->SUPER::new(%args); |
|
50
|
|
|
|
|
|
|
} |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# Getting an HTTP::Response |
|
53
|
|
|
|
|
|
|
sub get_response { |
|
54
|
|
|
|
|
|
|
my $self = shift; |
|
55
|
|
|
|
|
|
|
my $url = shift; |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
return $self->_get_ua($url); |
|
58
|
|
|
|
|
|
|
} |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# Getting the response contents as a scalar or decoded to a data structure |
|
61
|
|
|
|
|
|
|
sub get { |
|
62
|
|
|
|
|
|
|
my $self = shift; |
|
63
|
|
|
|
|
|
|
my $resp = shift; |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
return $self->_get_output($resp, wantarray); |
|
66
|
|
|
|
|
|
|
} |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
L is a base class for simple Perl Weather API clients. Apart |
|
71
|
|
|
|
|
|
|
from handling JSON and XML API responses (L and L required respectivelly), |
|
72
|
|
|
|
|
|
|
it offers utility functions for time and unit conversions, specifically useful for |
|
73
|
|
|
|
|
|
|
weather-related APIs. |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
This module was mainly created to streamline maintenance of the L, |
|
76
|
|
|
|
|
|
|
L and L modules by factoring out shared |
|
77
|
|
|
|
|
|
|
code. In the unlikely event that you'd like to base your own weather or similar |
|
78
|
|
|
|
|
|
|
API wrapper module on it, look at the implementation of those modules for guidance. |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 C |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
my $base = Weather::API::Base->new( |
|
85
|
|
|
|
|
|
|
timeout => $timeout_sec?, |
|
86
|
|
|
|
|
|
|
agent => $user_agent_string?, |
|
87
|
|
|
|
|
|
|
ua => $lwp_ua?, |
|
88
|
|
|
|
|
|
|
error => $die_or_return?, |
|
89
|
|
|
|
|
|
|
debug => $debug?, |
|
90
|
|
|
|
|
|
|
output => $output, |
|
91
|
|
|
|
|
|
|
scheme => $url_scheme? |
|
92
|
|
|
|
|
|
|
); |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Creates a Weather::API::Base object. As explained, you'd normally use a module that |
|
95
|
|
|
|
|
|
|
inherits from this, but the base class sets these defaults: |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
( |
|
98
|
|
|
|
|
|
|
timeout => 30, |
|
99
|
|
|
|
|
|
|
agent => "libwww-perl $package/$version", |
|
100
|
|
|
|
|
|
|
error => 'return', |
|
101
|
|
|
|
|
|
|
output => 'json', |
|
102
|
|
|
|
|
|
|
scheme => 'https', |
|
103
|
|
|
|
|
|
|
); |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
Parameters: |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=over 4 |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=item * C : Timeout for requests in secs. Default: C<30>. |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=item * C : Customize the user agent string. Default: C |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=item * C : Pass your own L to customize further. Will override C. |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=item * C : If there is an error response with the main methods, you have the options to C or C it. Default: C. |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=item * C : If debug mode is enabled, API URLs accessed are printed in STDERR when calling C<_get_ua>. Default: C. |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=item * C : You can use C as an option if it is supported by the API and you have trouble building https support for LWP in your system. Default: C. |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=item * C |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=back |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=head1 PRIVATE METHODS |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
These are to be used when subclassing. |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head2 C<_get_output> |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
$self->_get_output($response, wantarray); |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
C<$response> should be an L object, unless C<$self-E{curl}> is true |
|
134
|
|
|
|
|
|
|
in which case it should be a string. On C a Perl hash (or array) will be |
|
135
|
|
|
|
|
|
|
returned by decoding a JSON/XML response (if C<$self-E{output}> is C) or |
|
136
|
|
|
|
|
|
|
just the decoded content as a value for the C key otherwise. |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=head2 C<_get_ua> |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
my $resp = $self->_get_ua($url); |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
Will either use C<$self-E{ua}> or create a new one and fetch the C<$url> with it. |
|
143
|
|
|
|
|
|
|
If the URL does not contain the scheme, it will be applied from C<$self-E{scheme}>. |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head1 HELPER FUNCTIONS |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
Exportable helper/utility functions: |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=head2 C |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
my $result = convert_units($from, $to, $value); |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
Can convert from/to various units that are used in weather: |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=over 4 |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=item * B km/h, mph, m/s, Bft, kt |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=item * B K, F, C |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=item * B mm, in, m, km, mi |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=item * B atm, mbar, mmHg, kPa, hPa |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=back |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
Use the above units as string parameters. Example: |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
$result = convert_units('atm', 'mmHg', 1); # Will return 760 (mmHg per 1 atm) |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
If you try to convert between non convertible units, the croak message will list |
|
173
|
|
|
|
|
|
|
the valid conversions from the 'from' units. For example C |
|
174
|
|
|
|
|
|
|
will croak with the speed units (km/h, mph, m/s, Bft, kt) that are available to |
|
175
|
|
|
|
|
|
|
convert from km/h. |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
Note that the Beaufort scale (C) is an empirical scale commonly used in whole |
|
178
|
|
|
|
|
|
|
numbers (converting to a range of +/- 0.5 Bft in other units), but the convert |
|
179
|
|
|
|
|
|
|
function will actually give you the approximate floating point value based on an |
|
180
|
|
|
|
|
|
|
accepted empirical function. |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=head2 C |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
my $datetime = ts_to_date($timestamp, $utc?); |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
There are many ways to convert unix timestamps to human readable dates, but for |
|
187
|
|
|
|
|
|
|
convenience you can use C, which is a very fast function that will |
|
188
|
|
|
|
|
|
|
return the format C in your local time zone, or |
|
189
|
|
|
|
|
|
|
C in UTC if the second argument is true. |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=head2 C |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
my $datetime = ts_to_iso_date($timestamp, $utc?); |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
Same as C but returns a strict ISO date with the C date/time |
|
196
|
|
|
|
|
|
|
separator. |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head2 C |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
my $ts = datetime_to_ts($datetime, $utc?); |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
Fast function that accepts C or C and converts |
|
203
|
|
|
|
|
|
|
to a timestamp (for midnight in the former case). Will use local timezone unless |
|
204
|
|
|
|
|
|
|
you either pass a true second argument or use datetime with the C (Zulu time) |
|
205
|
|
|
|
|
|
|
suffix. Accepts any date/time divider, so strict ISO with C will work as well. |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=head2 C |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
my $month_no = mon_to_num($month_abbrev, $pad_zero?); |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
Takes a 3-letter English month abbreviation and returns the month number (1-12, |
|
212
|
|
|
|
|
|
|
zero-padded if second argument is true). Case insensitive. |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=head2 C |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
my $month_abbr = num_to_mon($month_no); |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
Takes the month number (1-12) and returns the 3-letter English month abbreviation |
|
219
|
|
|
|
|
|
|
(capital first letter). |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=cut |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
my $geocache; |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
sub new { |
|
226
|
6
|
|
|
6
|
1
|
494746
|
my $class = shift; |
|
227
|
|
|
|
|
|
|
|
|
228
|
6
|
|
|
|
|
11
|
my $self = {}; |
|
229
|
6
|
|
|
|
|
15
|
bless($self, $class); |
|
230
|
|
|
|
|
|
|
|
|
231
|
6
|
|
|
|
|
21
|
my %args = @_; |
|
232
|
6
|
|
|
|
|
20
|
my ($package) = caller; |
|
233
|
6
|
100
|
|
|
|
53
|
$package = __PACKAGE__ if $package eq 'main'; |
|
234
|
6
|
|
|
|
|
69
|
my $version = $package->VERSION; |
|
235
|
|
|
|
|
|
|
|
|
236
|
6
|
|
|
|
|
98
|
my %defaults = ( |
|
237
|
|
|
|
|
|
|
scheme => 'https', |
|
238
|
|
|
|
|
|
|
timeout => 30, |
|
239
|
|
|
|
|
|
|
agent => "libwww-perl $package/$version", |
|
240
|
|
|
|
|
|
|
output => 'json', |
|
241
|
|
|
|
|
|
|
units => 'metric', |
|
242
|
|
|
|
|
|
|
error => 'return', |
|
243
|
|
|
|
|
|
|
); |
|
244
|
6
|
100
|
|
|
|
26
|
$args{agent} = $args{ua}->agent() if $args{ua}; |
|
245
|
6
|
|
66
|
|
|
160
|
$self->{$_} = $args{$_} || $defaults{$_} for keys %defaults; |
|
246
|
6
|
|
|
|
|
55
|
$self->{$_} = $args{$_} for qw/ua debug curl language lang/; |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
croak("http or https scheme expected") |
|
249
|
6
|
100
|
100
|
|
|
178
|
if $self->{scheme} ne 'http' && $self->{scheme} ne 'https'; |
|
250
|
|
|
|
|
|
|
|
|
251
|
5
|
|
|
|
|
22
|
return $self; |
|
252
|
|
|
|
|
|
|
} |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
sub ts_to_date { |
|
255
|
2
|
|
|
2
|
1
|
3740
|
return _ts_to_date(@_); |
|
256
|
|
|
|
|
|
|
} |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
sub ts_to_iso_date { |
|
259
|
2
|
|
|
2
|
1
|
2303
|
return _ts_to_date($_[0], $_[1], 'T'); |
|
260
|
|
|
|
|
|
|
} |
|
261
|
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
sub _ts_to_date { |
|
263
|
4
|
|
|
4
|
|
10
|
my $ts = shift; |
|
264
|
4
|
|
|
|
|
8
|
my $gm = shift; |
|
265
|
4
|
|
100
|
|
|
20
|
my $iso = shift || ' '; |
|
266
|
4
|
100
|
|
|
|
44
|
$gm = $gm ? 'Z' : ''; |
|
267
|
4
|
100
|
|
|
|
72
|
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = |
|
268
|
|
|
|
|
|
|
$gm ? gmtime($ts) : localtime($ts); |
|
269
|
4
|
|
|
|
|
9
|
$mon++; |
|
270
|
4
|
|
|
|
|
9
|
$year += 1900; |
|
271
|
4
|
|
|
|
|
51
|
return sprintf "%04d-%02d-%02d%s%02d:%02d:%02d%s", $year, $mon, $mday, $iso, |
|
272
|
|
|
|
|
|
|
$hour, $min, $sec, $gm; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
sub datetime_to_ts { |
|
276
|
5
|
|
|
5
|
1
|
6531
|
my $date = shift; |
|
277
|
5
|
|
|
|
|
23
|
my $gm = shift; |
|
278
|
5
|
100
|
100
|
|
|
87
|
return ($7 || $gm) |
|
|
|
100
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
? timegm($6, $5, $4, $3, $2 - 1, $1) |
|
280
|
|
|
|
|
|
|
: timelocal($6, $5, $4, $3, $2 - 1, $1) |
|
281
|
|
|
|
|
|
|
if $date =~ |
|
282
|
|
|
|
|
|
|
/(\d{4})-(\d{2})-(\d{2})(?:[ _Tt](\d{2}):(\d{2}):(\d{2})([Zz])?)?/; |
|
283
|
|
|
|
|
|
|
|
|
284
|
1
|
|
|
|
|
120
|
croak("Unrecognized date format (try 'YYYY-MM-DD' or 'YYYY-MM-DD HH:mm:ss')"); |
|
285
|
|
|
|
|
|
|
} |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
sub mon_to_num { |
|
288
|
5
|
|
|
5
|
1
|
2484
|
my $month = shift; |
|
289
|
5
|
|
|
|
|
9
|
my $pad = shift; |
|
290
|
|
|
|
|
|
|
|
|
291
|
5
|
100
|
|
|
|
20
|
return unless $month; |
|
292
|
|
|
|
|
|
|
|
|
293
|
4
|
|
|
|
|
7
|
my %map; |
|
294
|
4
|
|
|
|
|
56
|
$map{lc($months[$_-1])} = $_ for 1..12; |
|
295
|
|
|
|
|
|
|
|
|
296
|
4
|
|
|
|
|
9
|
my $num = $map{lc($month)}; |
|
297
|
|
|
|
|
|
|
|
|
298
|
4
|
100
|
100
|
|
|
28
|
return "0$num" if $pad && length($num) < 2; |
|
299
|
|
|
|
|
|
|
|
|
300
|
3
|
|
|
|
|
16
|
return $num; |
|
301
|
|
|
|
|
|
|
} |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
sub num_to_mon { |
|
304
|
3
|
|
|
3
|
1
|
4367
|
my $num = shift; |
|
305
|
3
|
100
|
66
|
|
|
55
|
return unless $num && $num > 0 && $num < 13; |
|
|
|
|
100
|
|
|
|
|
|
306
|
|
|
|
|
|
|
|
|
307
|
1
|
|
|
|
|
5
|
return $months[$num-1]; |
|
308
|
|
|
|
|
|
|
} |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
sub _verify_lat_lon { |
|
311
|
5
|
|
|
5
|
|
3119
|
my $args = shift; |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
croak("lat between -90 and 90 expected") |
|
314
|
5
|
100
|
100
|
|
|
171
|
unless defined $args->{lat} && abs($args->{lat}) <= 90; |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
croak("lon between -180 and 180 expected") |
|
317
|
3
|
100
|
100
|
|
|
158
|
unless defined $args->{lon} && abs($args->{lon}) <= 180; |
|
318
|
|
|
|
|
|
|
} |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
sub _get_output { |
|
321
|
9
|
|
|
9
|
|
13418
|
my $self = shift; |
|
322
|
9
|
|
|
|
|
34
|
my $resp = shift; |
|
323
|
9
|
|
|
|
|
15
|
my $wantarr = shift; |
|
324
|
9
|
100
|
|
|
|
30
|
my $output = $wantarr ? $self->{output} : ''; |
|
325
|
|
|
|
|
|
|
|
|
326
|
9
|
100
|
|
|
|
34
|
return _output($resp, $output) if $self->{curl}; |
|
327
|
|
|
|
|
|
|
|
|
328
|
8
|
100
|
|
|
|
36
|
if ($resp->is_success) { |
|
329
|
5
|
|
|
|
|
71
|
return _output($resp->decoded_content, $output); |
|
330
|
|
|
|
|
|
|
} else { |
|
331
|
3
|
100
|
66
|
|
|
41
|
if ($self->{error} && $self->{error} eq 'die') { |
|
332
|
1
|
|
|
|
|
4
|
die $resp->status_line; |
|
333
|
|
|
|
|
|
|
} else { |
|
334
|
2
|
100
|
|
|
|
17
|
return $wantarr ? (error => $resp) : "ERROR: ".$resp->status_line; |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
} |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
sub _get_ua { |
|
340
|
3
|
|
|
3
|
|
1629
|
my $self = shift; |
|
341
|
3
|
|
|
|
|
32
|
my $url = shift; |
|
342
|
3
|
100
|
|
|
|
16
|
$url = $self->{scheme}.'://'.$url unless $url =~ /^https?:/; |
|
343
|
|
|
|
|
|
|
|
|
344
|
3
|
100
|
|
|
|
12
|
warn "$url\n" if $self->{debug}; |
|
345
|
|
|
|
|
|
|
|
|
346
|
3
|
100
|
|
|
|
12
|
$self->_ua unless $self->{ua}; |
|
347
|
|
|
|
|
|
|
|
|
348
|
3
|
|
|
|
|
48
|
return $self->{ua}->get($url); |
|
349
|
|
|
|
|
|
|
} |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
sub _ua { |
|
352
|
1
|
|
|
1
|
|
2
|
my $self = shift; |
|
353
|
|
|
|
|
|
|
|
|
354
|
1
|
|
|
|
|
26
|
$self->{ua} = LWP::UserAgent->new(); |
|
355
|
1
|
|
|
|
|
252
|
$self->{ua}->agent($self->{agent}); |
|
356
|
1
|
|
|
|
|
46
|
$self->{ua}->timeout($self->{timeout}); |
|
357
|
|
|
|
|
|
|
} |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
sub _output { |
|
360
|
6
|
|
|
6
|
|
954
|
my $str = shift; |
|
361
|
6
|
|
|
|
|
12
|
my $format = shift; |
|
362
|
|
|
|
|
|
|
|
|
363
|
6
|
100
|
|
|
|
32
|
return $str unless $format; |
|
364
|
|
|
|
|
|
|
|
|
365
|
4
|
100
|
|
|
|
22
|
if ($format eq 'json') { |
|
|
|
100
|
|
|
|
|
|
|
366
|
1
|
|
|
|
|
1512
|
require JSON; |
|
367
|
1
|
|
|
|
|
14141
|
return _deref(JSON::decode_json($str)); |
|
368
|
|
|
|
|
|
|
} elsif ($format eq 'xml') { |
|
369
|
2
|
|
|
|
|
16
|
require XML::Simple; |
|
370
|
2
|
|
|
|
|
9
|
return _deref(XML::Simple::XMLin($str)); |
|
371
|
|
|
|
|
|
|
} |
|
372
|
1
|
|
|
|
|
11
|
return (data => $str); |
|
373
|
|
|
|
|
|
|
} |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
sub _deref { |
|
376
|
7
|
|
|
7
|
|
99810
|
my $ref = shift; |
|
377
|
7
|
100
|
|
|
|
33
|
die "Could not decode response body" unless $ref; |
|
378
|
6
|
100
|
|
|
|
19
|
return $ref unless ref($ref); |
|
379
|
5
|
100
|
|
|
|
68
|
return %$ref if ref($ref) eq 'HASH'; |
|
380
|
1
|
|
|
|
|
4
|
return @$ref; |
|
381
|
|
|
|
|
|
|
} |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
my %units = ( |
|
384
|
|
|
|
|
|
|
'km/h' => [1000 / 3600, 'm/s'], |
|
385
|
|
|
|
|
|
|
mph => [1609.344 / 3600, 'm/s'], |
|
386
|
|
|
|
|
|
|
Bft => [\&_beaufort, 'm/s'], |
|
387
|
|
|
|
|
|
|
kt => [0.514444, 'm/s'], |
|
388
|
|
|
|
|
|
|
'm/s' => [1, 'm/s'], |
|
389
|
|
|
|
|
|
|
in => [0.0254, 'm'], |
|
390
|
|
|
|
|
|
|
mm => [0.001, 'm'], |
|
391
|
|
|
|
|
|
|
mi => [1609.344, 'm'], |
|
392
|
|
|
|
|
|
|
m => [1, 'm'], |
|
393
|
|
|
|
|
|
|
km => [1000, 'm'], |
|
394
|
|
|
|
|
|
|
atm => [1, 'atm'], |
|
395
|
|
|
|
|
|
|
mbar => [1 / 1013.25, 'atm'], |
|
396
|
|
|
|
|
|
|
mmHg => [1 / 760, 'atm'], |
|
397
|
|
|
|
|
|
|
hPa => [1 / 1013.25, 'atm'], |
|
398
|
|
|
|
|
|
|
kPa => [1 / 101.325, 'atm'], |
|
399
|
|
|
|
|
|
|
K => [\&_kelvin, 'C'], |
|
400
|
|
|
|
|
|
|
F => [\&_fahr, 'C'], |
|
401
|
|
|
|
|
|
|
C => [1, 'C'], |
|
402
|
|
|
|
|
|
|
); |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
sub _units { |
|
405
|
2
|
|
|
2
|
|
26
|
my $conv = shift; |
|
406
|
2
|
50
|
|
|
|
17
|
my @list = sort {$units{$b} cmp $units{$a} || $a cmp $b} keys %units; |
|
|
110
|
|
|
|
|
222
|
|
|
407
|
2
|
100
|
|
|
|
92
|
return join(', ', @list) unless $conv; |
|
408
|
1
|
100
|
66
|
|
|
4
|
my @ok = map {($units{$_}->[1] && $units{$_}->[1] ne $_) ? $_ : ()} @list; |
|
|
18
|
|
|
|
|
70
|
|
|
409
|
1
|
|
|
|
|
143
|
return join(', ', @ok); |
|
410
|
|
|
|
|
|
|
} |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub convert_units { |
|
413
|
19
|
|
|
19
|
1
|
13248
|
my ($from, $to, $val) = @_; |
|
414
|
|
|
|
|
|
|
|
|
415
|
19
|
100
|
|
|
|
129
|
croak "Value not defined." unless defined $val; |
|
416
|
|
|
|
|
|
|
|
|
417
|
18
|
|
|
|
|
45
|
foreach ($from, $to) { |
|
418
|
35
|
100
|
|
|
|
117
|
croak "$_ not recognized. Supported units: "._units unless $units{$_}; |
|
419
|
|
|
|
|
|
|
} |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
croak "Cannot convert to $to. Can only convert $from to: "._units($from) |
|
422
|
17
|
100
|
|
|
|
68
|
unless $units{$from}->[1] eq $units{$to}->[1]; |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
$val = |
|
425
|
|
|
|
|
|
|
ref($units{$from}->[0]) |
|
426
|
|
|
|
|
|
|
? $units{$from}->[0]->($val) |
|
427
|
16
|
100
|
|
|
|
63
|
: $val * $units{$from}->[0]; |
|
428
|
|
|
|
|
|
|
|
|
429
|
16
|
100
|
|
|
|
60
|
return $val if $units{$from}->[1] eq $to; |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
return |
|
432
|
|
|
|
|
|
|
ref($units{$to}->[0]) |
|
433
|
|
|
|
|
|
|
? $units{$to}->[0]->($val, 1) |
|
434
|
11
|
100
|
|
|
|
57
|
: $val / $units{$to}->[0]; |
|
435
|
|
|
|
|
|
|
} |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
sub _kelvin { |
|
438
|
2
|
|
|
2
|
|
5
|
my $val = shift; |
|
439
|
2
|
100
|
|
|
|
7
|
my $mult = shift() ? 1 : -1; |
|
440
|
|
|
|
|
|
|
|
|
441
|
2
|
|
|
|
|
12
|
return $val + $mult * 273.15; |
|
442
|
|
|
|
|
|
|
} |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
sub _fahr { |
|
445
|
2
|
|
|
2
|
|
5
|
my $val = shift; |
|
446
|
2
|
|
|
|
|
6
|
my $rev = shift; |
|
447
|
|
|
|
|
|
|
|
|
448
|
2
|
100
|
|
|
|
12
|
return $val * 9 / 5 + 32 if $rev; |
|
449
|
1
|
|
|
|
|
6
|
return ($val - 32) * 5 / 9; |
|
450
|
|
|
|
|
|
|
} |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
sub _beaufort { |
|
453
|
2
|
|
|
2
|
|
6
|
my $val = shift; |
|
454
|
2
|
|
|
|
|
5
|
my $rev = shift; |
|
455
|
|
|
|
|
|
|
|
|
456
|
2
|
100
|
|
|
|
12
|
return ($val / 0.836)**(2 / 3) if $rev; |
|
457
|
1
|
|
|
|
|
7
|
return 0.836 * ($val**1.5); |
|
458
|
|
|
|
|
|
|
} |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
=head1 RELATED WEATHER MODULES |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
A quick listing of Perl modules that are based on L: |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=head2 L |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
If you are interested in astronomy/stargazing the 7Timer! weather forecast might be |
|
467
|
|
|
|
|
|
|
very useful. It uses the standard NOAA forecast, but calculates astronomical seeing |
|
468
|
|
|
|
|
|
|
and transparency. It is completely free, no API key needed. |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=head2 L |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
OpenWeatherMap uses various weather sources combined with their own ML and offers |
|
473
|
|
|
|
|
|
|
a couple of free endpoints (the v2.5 current weather and 5d/3h forecast) with generous |
|
474
|
|
|
|
|
|
|
request limits. Their newer One Call 3.0 API also offers some free usage (1000 calls/day) |
|
475
|
|
|
|
|
|
|
and the cost is per call above that. If you want access to history APIs, extended |
|
476
|
|
|
|
|
|
|
hourly forecasts etc, there are monthly subscriptions. |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=head2 L |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
An alternative source for multi-source forecasts is Apple's WeatherKit (based on |
|
481
|
|
|
|
|
|
|
the old Dark Sky weather API). It offers 500k calls/day for free, but requires a |
|
482
|
|
|
|
|
|
|
paid Apple developer account. |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=head2 L |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
Simple client for NOAA's Aurora Forecast Service. |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=head1 AUTHOR |
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
Dimitrios Kechagias, C<< >> |
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
=head1 BUGS |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
Please report any bugs or feature requests on L. |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=head1 GIT |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
L |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
This software is copyright (c) 2024 by Dimitrios Kechagias. |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
|
505
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=cut |
|
508
|
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
1; |