File Coverage

blib/lib/Weather/Meteo.pm
Criterion Covered Total %
statement 32 77 41.5
branch 5 32 15.6
condition 1 13 7.6
subroutine 8 10 80.0
pod 3 3 100.0
total 49 135 36.3


line stmt bran cond sub pod time code
1             package Weather::Meteo;
2              
3 3     3   321962 use strict;
  3         18  
  3         87  
4 3     3   16 use warnings;
  3         5  
  3         79  
5              
6 3     3   14 use Carp;
  3         5  
  3         196  
7 3     3   987 use JSON::MaybeXS;
  3         11544  
  3         167  
8 3     3   2249 use LWP::UserAgent;
  3         157898  
  3         132  
9 3     3   29 use URI;
  3         7  
  3         94  
10              
11 3     3   19 use constant FIRST_YEAR => 1940;
  3         10  
  3         2405  
12              
13             =head1 NAME
14              
15             Weather::Meteo - Interface to L for historical weather data
16              
17             =head1 VERSION
18              
19             Version 0.06
20              
21             =cut
22              
23             our $VERSION = '0.06';
24              
25             =head1 SYNOPSIS
26              
27             use Weather::Meteo;
28              
29             my $meteo = Weather::Meteo->new();
30             my $weather = $meteo->weather({ latitude => 0.1, longitude => 0.2, date => '2022-12-25' });
31              
32             =head1 DESCRIPTION
33              
34             Weather::Meteo provides an interface to open-meteo.com
35             for historical weather data from 1940.
36              
37             =head1 METHODS
38              
39             =head2 new
40              
41             my $meteo = Weather::Meteo->new();
42             my $ua = LWP::UserAgent->new();
43             $ua->env_proxy(1);
44             $meteo = Weather::Meteo->new(ua => $ua);
45              
46             my $weather = $meteo->weather({ latitude => 51.34, longitude => 1.42, date => '2022-12-25' });
47             my @snowfall = @{$weather->{'hourly'}->{'snowfall'}};
48              
49             print 'Number of cms of snow: ', $snowfall[1], "\n";
50              
51             =cut
52              
53             sub new {
54 5     5 1 850 my($class, %args) = @_;
55              
56 5 100       22 if(!defined($class)) {
    100          
57             # Weather::Meteo::new() used rather than Weather::Meteo->new()
58 1         2 $class = __PACKAGE__;
59             } elsif(ref($class)) {
60             # clone the given object
61 1         30 return bless { %{$class}, %args }, ref($class);
  1         28  
62             }
63              
64 4         8 my $ua = $args{ua};
65 4 50       12 if(!defined($ua)) {
66 4         30 $ua = LWP::UserAgent->new(agent => __PACKAGE__ . "/$VERSION");
67 4         6670 $ua->default_header(accept_encoding => 'gzip,deflate');
68             }
69 4   50     235 my $host = $args{host} || 'archive-api.open-meteo.com';
70              
71 4         30 return bless { ua => $ua, host => $host }, $class;
72             }
73              
74             =head2 weather
75              
76             use Geo::Location::Point;
77              
78             my $ramsgate = Geo::Location::Point->new({ latitude => 51.34, longitude => 1.42 });
79             # Print snowfall at 1AM on Christmas morning in Ramsgate
80             $weather = $meteo->weather($ramsgate, '2022-12-25');
81             @snowfall = @{$weather->{'hourly'}->{'snowfall'}};
82              
83             print 'Number of cms of snow: ', $snowfall[1], "\n";
84              
85             Takes an optional argument, tz, which defaults to 'Europe/London'.
86             For that to work set TIMEZONEDB_KEY to be your API key from L.
87              
88             =cut
89              
90             sub weather {
91 0     0 1   my $self = shift;
92 0           my %param;
93              
94 0 0 0       if(ref($_[0]) eq 'HASH') {
    0 0        
    0          
    0          
95 0           %param = %{$_[0]};
  0            
96             } elsif((@_ == 2) && (ref($_[0]) =~ /::/) && ($_[0]->can('latitude'))) {
97 0           my $location = $_[0];
98 0           $param{latitude} = $location->latitude();
99 0           $param{longitude} = $location->longitude();
100 0           $param{'date'} = $_[1];
101 0 0 0       if($_[0]->can('tz') && $ENV{'TIMEZONEDB_KEY'}) {
102 0           $param{'tz'} = $_[0]->tz();
103             }
104             } elsif(ref($_[0])) {
105 0           Carp::croak('Usage: weather(latitude => $latitude, longitude => $logitude, date => "YYYY-MM-DD" [ , tz = $tz ])');
106 0           return;
107             } elsif(@_ % 2 == 0) {
108 0           %param = @_;
109             }
110              
111 0           my $latitude = $param{latitude};
112 0           my $longitude = $param{longitude};
113 0           my $date = $param{'date'};
114 0   0       my $tz = $param{'tz'} || 'Europe/London';
115              
116 0 0         if(!defined($latitude)) {
117 0           Carp::croak('Usage: weather(latitude => $latitude, longitude => $logitude, date => "YYYY-MM-DD")');
118 0           return;
119             }
120              
121 0 0         if($date =~ /^(\d{4})-/) {
122 0           my $year = $1;
123              
124 0 0         return if($1 < FIRST_YEAR);
125             } else {
126 0           Carp::carp("'$date' is not a valid date");
127 0           return;
128             }
129              
130 0           my $uri = URI->new("https://$self->{host}/v1/archive");
131 0           my %query_parameters = (
132             'latitude' => $latitude,
133             'longitude' => $longitude,
134             'start_date' => $date,
135             'end_date' => $date,
136             'hourly' => 'temperature_2m,rain,snowfall,weathercode',
137             'daily' => 'weathercode,temperature_2m_max,temperature_2m_min,rain_sum,snowfall_sum,precipitation_hours,windspeed_10m_max,windgusts_10m_max',
138             'timezone' => $tz,
139             # https://stackoverflow.com/questions/16086962/how-to-get-a-time-zone-from-a-location-using-latitude-and-longitude-coordinates
140             'windspeed_unit' => 'mph',
141             'precipitation_unit' => 'inch'
142             );
143              
144 0           $uri->query_form(%query_parameters);
145 0           my $url = $uri->as_string();
146              
147 0           $url =~ s/%2C/,/g;
148              
149 0           my $res = $self->{ua}->get($url);
150              
151 0 0         if($res->is_error()) {
152 0           Carp::croak("$url API returned error: ", $res->status_line());
153 0           return;
154             }
155             # $res->content_type('text/plain'); # May be needed to decode correctly
156              
157 0           my $json = JSON::MaybeXS->new()->utf8();
158 0 0         if(my $rc = $json->decode($res->decoded_content())) {
159 0 0         if($rc->{'error'}) {
160             # TODO: print error code
161 0           return;
162             }
163 0 0         if(defined($rc->{'hourly'})) {
164 0           return $rc; # No support for list context, yet
165             }
166             }
167              
168             # my @results = @{ $data || [] };
169             # wantarray ? @results : $results[0];
170             }
171              
172             =head2 ua
173              
174             Accessor method to get and set UserAgent object used internally. You
175             can call I for example, to get the proxy information from
176             environment variables:
177              
178             $meteo->ua()->env_proxy(1);
179              
180             You can also set your own User-Agent object:
181              
182             use LWP::UserAgent::Throttled;
183              
184             my $ua = LWP::UserAgent::Throttled->new();
185             $ua->throttle('open-meteo.com' => 1);
186             $meteo->ua($ua);
187              
188             =cut
189              
190             sub ua {
191 0     0 1   my $self = shift;
192 0 0         if (@_) {
193 0           $self->{ua} = shift;
194             }
195 0           $self->{ua};
196             }
197              
198             =head1 AUTHOR
199              
200             Nigel Horne, C<< >>
201              
202             This library is free software; you can redistribute it and/or modify
203             it under the same terms as Perl itself.
204              
205             Lots of thanks to the folks at L.
206              
207             =head1 BUGS
208              
209             =head1 SEE ALSO
210              
211             Open Meteo API: L
212              
213             =head1 SUPPORT
214              
215             You can find documentation for this module with the perldoc command.
216              
217             perldoc Weather::Meteo
218              
219             You can also look for information at:
220              
221             =over 4
222              
223             =item * MetaCPAN
224              
225             L
226              
227             =item * RT: CPAN's request tracker
228              
229             L
230              
231             =item * CPANTS
232              
233             L
234              
235             =item * CPAN Testers' Matrix
236              
237             L
238              
239             =item * CPAN Testers Dependencies
240              
241             L
242              
243             =back
244              
245             =head1 LICENSE AND COPYRIGHT
246              
247             Copyright 2023 Nigel Horne.
248              
249             This program is released under the following licence: GPL2
250              
251             =cut
252              
253             1;