File Coverage

blib/lib/Weather/YR/LocationForecast.pm
Criterion Covered Total %
statement 97 112 86.6
branch 6 12 50.0
condition 1 2 50.0
subroutine 24 26 92.3
pod n/a
total 128 152 84.2


line stmt bran cond sub pod time code
1             package Weather::YR::LocationForecast;
2 3     3   27 use Moose;
  3         6  
  3         19  
3 3     3   20484 use namespace::autoclean;
  3         6  
  3         16  
4              
5             extends 'Weather::YR::Base';
6              
7 3     3   4474 use DateTime;
  3         1339424  
  3         169  
8 3     3   2940 use DateTime::Format::ISO8601;
  3         402346  
  3         183  
9 3     3   1939 use Mojo::URL;
  3         398536  
  3         27  
10              
11 3     3   1824 use Weather::YR::LocationForecast::DataPoint;
  3         17  
  3         166  
12 3     3   1842 use Weather::YR::LocationForecast::Day;
  3         14  
  3         184  
13              
14 3     3   2004 use Weather::YR::Model::Cloudiness;
  3         14  
  3         142  
15 3     3   1854 use Weather::YR::Model::DewPointTemperature;
  3         12  
  3         131  
16 3     3   1796 use Weather::YR::Model::Fog;
  3         12  
  3         124  
17 3     3   1618 use Weather::YR::Model::Humidity;
  3         10  
  3         132  
18 3     3   1664 use Weather::YR::Model::Precipitation::Symbol;
  3         12  
  3         153  
19 3     3   1722 use Weather::YR::Model::Precipitation;
  3         13  
  3         160  
20 3     3   1757 use Weather::YR::Model::Pressure;
  3         14  
  3         130  
21 3     3   1708 use Weather::YR::Model::Probability::Temperature;
  3         15  
  3         137  
22 3     3   1845 use Weather::YR::Model::Probability::Wind;
  3         9  
  3         124  
23 3     3   26 use Weather::YR::Model::Temperature;
  3         7  
  3         86  
24 3     3   1681 use Weather::YR::Model::WindDirection;
  3         13  
  3         132  
25 3     3   1706 use Weather::YR::Model::WindSpeed;
  3         12  
  3         3842  
26              
27             =head1 NAME
28              
29             Weather::YR::LocationForecast - Object-oriented interface to Yr.no's "location
30             forecast" API.
31              
32             =head1 DESCRIPTION
33              
34             Don't use this class directly. Instead, access it from the L<Weather::YR> class.
35              
36             =cut
37              
38             has 'status_code' => ( isa => 'Num', is => 'rw', required => 0 );
39              
40             has 'url' => ( isa => 'Mojo::URL', is => 'ro', lazy_build => 1 );
41             has 'schema_url' => ( isa => 'Mojo::URL', is => 'ro', lazy_build => 1 );
42             has 'datapoints' => ( isa => 'ArrayRef[Weather::YR::LocationForecast::DataPoint]', is => 'ro', lazy_build => 1 );
43             has 'days' => ( isa => 'ArrayRef[Weather::YR::LocationForecast::Day]', is => 'ro', lazy_build => 1 );
44              
45             has 'now' => ( isa => 'Weather::YR::LocationForecast::Day', is => 'ro', lazy_build => 1 );
46             has 'today' => ( isa => 'Weather::YR::LocationForecast::Day', is => 'ro', lazy_build => 1 );
47             has 'tomorrow' => ( isa => 'Weather::YR::LocationForecast::Day', is => 'ro', lazy_build => 1 );
48              
49             =head1 METHODS
50              
51             =head2 url
52              
53             Returns the URL to YR.no's location forecast service. This is handy if you
54             want to retrieve the XML from YR.no yourself;
55              
56             my $yr = Weather::YR->new(
57             lat => 63.5908,
58             lon => 10.7414,
59             );
60              
61             my $url = $yr->location_forecast->url;
62              
63             my $xml = MyFancyHttpClient->new->get( $url );
64              
65             my $yr = Weather::YR->new(
66             xml => $xml,
67             tz => DateTime::TimeZone->new( name => 'Europe/Oslo' ),
68             );
69              
70             my $forecast = $yr->location_forecast;
71              
72             =cut
73              
74             sub _build_url {
75 1     1   3 my $self = shift;
76              
77 1         70 my $url = $self->service_url->clone;
78 1         64 $url->path ( '/weatherapi/locationforecast/2.0/classic' );
79 1         162 $url->query(
80             lat => $self->lat,
81             lon => $self->lon,
82             altitude => $self->msl,
83             );
84              
85 1         160 return $url;
86             }
87              
88             =head2 schema_url
89              
90             Returns the URL to YR.no' location forecast service XML schema. This is used
91             internally for validating the XML output from YR.no itself.
92              
93             =cut
94              
95             sub _build_schema_url {
96 1     1   3 my $self = shift;
97              
98 1         39 my $url = $self->service_url->clone;
99 1         94 $url->path( '/weatherapi/locationforecast/2.0/schema' );
100              
101 1         61 return $url;
102             }
103              
104             =head2 datapoints
105              
106             Returns an array reference of L<Weather::YR::LocationForecast::DataPoint> instances.
107              
108             =cut
109              
110             sub _build_datapoints {
111 1     1   2 my $self = shift;
112              
113 1         7 my @datapoints = ();
114              
115 1 50       30 if ( my $xml_ref = $self->xml_ref ) {
116 1   50     7 my $times = $xml_ref->{product}->{time} || [];
117 1         2 my $datapoint = undef;
118              
119 1         4 foreach my $t ( @{$times} ) {
  1         4  
120 342         1712 my $from = $self->date_to_datetime( $t->{from} );
121 342         1431 my $to = $self->date_to_datetime( $t->{to } );
122              
123 342 100       1984 if ( $t->{location}->{temperature} ) {
    50          
124 84         155 my $loc = $t->{location};
125              
126 84 100       235 if ( defined $datapoint ) {
127 83         188 push( @datapoints, $datapoint );
128 83         148 $datapoint = undef;
129             }
130              
131             $datapoint = Weather::YR::LocationForecast::DataPoint->new(
132             from => $from,
133             to => $to,
134             lang => $self->lang,
135             type => $t->{datatype},
136              
137             temperature => Weather::YR::Model::Temperature->new(
138             from => $from,
139             to => $to,
140             lang => $self->lang,
141             celsius => $loc->{temperature}->{value},
142             ),
143              
144             wind_direction => Weather::YR::Model::WindDirection->new(
145             from => $from,
146             to => $to,
147             lang => $self->lang,
148             degrees => $loc->{windDirection}->{deg},
149             name => $loc->{windDirection}->{name},
150             ),
151              
152             wind_speed => Weather::YR::Model::WindSpeed->new(
153             from => $from,
154             to => $to,
155             lang => $self->lang,
156             mps => $loc->{windSpeed}->{mps},
157             beaufort => $loc->{windSpeed}->{beaufort},
158             name => $loc->{windSpeed}->{name},
159             ),
160              
161             humidity => Weather::YR::Model::Humidity->new(
162             from => $from,
163             to => $to,
164             lang => $self->lang,
165             percent => $loc->{humidity}->{value},
166             ),
167              
168             pressure => Weather::YR::Model::Pressure->new(
169             from => $from,
170             to => $to,
171             lang => $self->lang,
172             hPa => $loc->{pressure}->{value},
173             ),
174              
175             cloudiness => Weather::YR::Model::Cloudiness->new(
176             from => $from,
177             to => $to,
178             lang => $self->lang,
179             percent => $loc->{cloudiness}->{percent},
180             ),
181              
182             fog => Weather::YR::Model::Fog->new(
183             from => $from,
184             to => $to,
185             lang => $self->lang,
186             percent => $loc->{fog}->{percent},
187             ),
188              
189             dew_point_temperature => Weather::YR::Model::DewPointTemperature->new(
190             from => $from,
191             to => $to,
192             lang => $self->lang,
193             celsius => $loc->{dewpointTemperature}->{value},
194             ),
195              
196             temperature_probability => Weather::YR::Model::Probability::Temperature->new(
197             from => $from,
198             to => $to,
199             lang => $self->lang,
200             value => $loc->{temperatureProbability}->{value},
201             ),
202              
203             wind_probability => Weather::YR::Model::Probability::Wind->new(
204             from => $from,
205             to => $to,
206             lang => $self->lang,
207             value => $loc->{windProbability}->{value},
208 84         2698 ),
209             );
210             }
211             elsif ( my $p = $t->{location}->{precipitation} ) {
212             my $precipitation = Weather::YR::Model::Precipitation->new(
213             from => $from,
214             to => $to,
215             lang => $self->lang,
216             value => $p->{value},
217             min => $p->{minvalue},
218             max => $p->{maxvalue},
219             symbol => Weather::YR::Model::Precipitation::Symbol->new(
220             from => $from,
221             to => $to,
222             lang => $self->lang,
223             id => $t->{location}->{symbol}->{id},
224             number => $t->{location}->{symbol}->{number},
225 258         8209 ),
226             );
227              
228 258         1096 $datapoint->add_precipitation( $precipitation );
229             }
230             }
231             }
232             else {
233 0         0 warn "No XML to generate forecast from!";
234             }
235              
236 1         32 return \@datapoints;
237             }
238              
239             =head2 days
240              
241             Returns an array reference of L<Weather::YR::LocationForecast::Day> instances.
242              
243             =cut
244              
245             sub _build_days {
246 1     1   2 my $self = shift;
247              
248 1         4 my %day_datapoints = ();
249              
250 1         3 foreach my $datapoint ( @{$self->datapoints} ) {
  1         34  
251 83         921 push( @{$day_datapoints{$datapoint->{from}->ymd}}, $datapoint );
  83         207  
252             }
253              
254 1         13 my @days = ();
255              
256 1         11 foreach my $date ( sort keys %day_datapoints ) {
257             my $day = Weather::YR::LocationForecast::Day->new(
258             date => DateTime::Format::ISO8601->parse_datetime( $date ),
259 10         42 datapoints => $day_datapoints{ $date },
260             );
261              
262 10         30 push( @days, $day );
263             }
264              
265 1         35 return \@days;
266             }
267              
268             =head2 now
269              
270             Returns a L<Weather::YR::LocationForecast::Day> instance, representing the
271             closest forecast in time.
272              
273             =cut
274              
275             sub _build_now {
276 0     0   0 my $self = shift;
277              
278 0         0 my $now = DateTime->now( time_zone => 'UTC' );
279 0         0 my $closest_dp = undef;
280              
281 0         0 foreach my $dp ( @{$self->today->datapoints} ) {
  0         0  
282 0 0       0 unless ( defined $closest_dp ) {
283 0         0 $closest_dp = $dp;
284 0         0 next;
285             }
286              
287 0         0 my $diff_from_now = abs( $dp->from->epoch - $now->epoch );
288              
289 0 0       0 if ( $diff_from_now < ( abs($closest_dp->from->epoch - $now->epoch) ) ) {
290 0         0 $closest_dp = $dp;
291             }
292             }
293              
294 0         0 return Weather::YR::LocationForecast::Day->new(
295             date => $closest_dp->from,
296             datapoints => [ $closest_dp ],
297             );
298             }
299              
300             =head2 today
301              
302             Returns a L<Weather::YR::LocationForecast::Day> instance, representing today's
303             weather.
304              
305             =cut
306              
307             sub _build_today {
308 1     1   3 my $self = shift;
309              
310 1         31 return $self->days->[0];
311             }
312              
313             =head2 tomorrow
314              
315             Returns a L<Weather::YR::LocationForecast::Day> instance, representing
316             tomorrow's weather.
317              
318             =cut
319              
320             sub _build_tomorrow {
321 0     0     my $self = shift;
322              
323 0           return $self->days->[1];
324             }
325              
326             __PACKAGE__->meta->make_immutable;
327              
328             1;