File Coverage

blib/lib/Weather/Meteo.pm
Criterion Covered Total %
statement 32 74 43.2
branch 5 30 16.6
condition 1 8 12.5
subroutine 8 10 80.0
pod 3 3 100.0
total 49 125 39.2


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