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   24 use Moose;
  3         7  
  3         20  
3 3     3   20665 use namespace::autoclean;
  3         7  
  3         19  
4              
5             extends 'Weather::YR::Base';
6              
7 3     3   4733 use DateTime;
  3         1373422  
  3         160  
8 3     3   5346 use DateTime::Format::ISO8601;
  3         397864  
  3         162  
9 3     3   3843 use Mojo::URL;
  3         415768  
  3         23  
10              
11 3     3   2298 use Weather::YR::LocationForecast::DataPoint;
  3         12  
  3         140  
12 3     3   2530 use Weather::YR::LocationForecast::Day;
  3         13  
  3         153  
13              
14 3     3   2603 use Weather::YR::Model::Cloudiness;
  3         13  
  3         129  
15 3     3   2745 use Weather::YR::Model::DewPointTemperature;
  3         11  
  3         120  
16 3     3   2658 use Weather::YR::Model::Fog;
  3         11  
  3         108  
17 3     3   2179 use Weather::YR::Model::Humidity;
  3         9  
  3         109  
18 3     3   2077 use Weather::YR::Model::Precipitation::Symbol;
  3         13  
  3         117  
19 3     3   2668 use Weather::YR::Model::Precipitation;
  3         9  
  3         120  
20 3     3   2315 use Weather::YR::Model::Pressure;
  3         12  
  3         106  
21 3     3   2230 use Weather::YR::Model::Probability::Temperature;
  3         15  
  3         110  
22 3     3   2419 use Weather::YR::Model::Probability::Wind;
  3         11  
  3         107  
23 3     3   21 use Weather::YR::Model::Temperature;
  3         7  
  3         86  
24 3     3   2209 use Weather::YR::Model::WindDirection;
  3         11  
  3         113  
25 3     3   2390 use Weather::YR::Model::WindSpeed;
  3         11  
  3         3665  
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 = My FancyHttpClient->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   2 my $self = shift;
76              
77 1         30 my $url = $self->service_url->clone;
78 1         62 $url->path ( '/weatherapi/locationforecast/2.0/classic' );
79 1         31 $url->query(
80             lat => $self->lat_as_string,
81             lon => $self->lon_as_string,
82             altitude => $self->msl
83             );
84              
85 1         148 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         42 my $url = $self->service_url->clone;
99 1         73 $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         4 my @datapoints = ();
114              
115 1 50       30 if ( my $xml_ref = $self->xml_ref ) {
116             # use Data::Dumper;
117             # print STDOUT Dumper( $xml_ref );
118             # die;
119             # my $times = $xml_ref->{weatherdata}->{product}->{time} || [];
120 1   50     7 my $times = $xml_ref->{product}->{time} || [];
121 1         3 my $datapoint = undef;
122              
123 1         3 foreach my $t ( @{$times} ) {
  1         4  
124 342         1484 my $from = $self->date_to_datetime( $t->{from} );
125 342         1271 my $to = $self->date_to_datetime( $t->{to } );
126              
127 342 100       1846 if ( $t->{location}->{temperature} ) {
    50          
128 84         151 my $loc = $t->{location};
129              
130 84 100       203 if ( defined $datapoint ) {
131 83         176 push( @datapoints, $datapoint );
132 83         137 $datapoint = undef;
133             }
134              
135             $datapoint = Weather::YR::LocationForecast::DataPoint->new(
136             from => $from,
137             to => $to,
138             lang => $self->lang,
139             type => $t->{datatype},
140              
141             temperature => Weather::YR::Model::Temperature->new(
142             from => $from,
143             to => $to,
144             lang => $self->lang,
145             celsius => $loc->{temperature}->{value},
146             ),
147              
148             wind_direction => Weather::YR::Model::WindDirection->new(
149             from => $from,
150             to => $to,
151             lang => $self->lang,
152             degrees => $loc->{windDirection}->{deg},
153             name => $loc->{windDirection}->{name},
154             ),
155              
156             wind_speed => Weather::YR::Model::WindSpeed->new(
157             from => $from,
158             to => $to,
159             lang => $self->lang,
160             mps => $loc->{windSpeed}->{mps},
161             beaufort => $loc->{windSpeed}->{beaufort},
162             name => $loc->{windSpeed}->{name},
163             ),
164              
165             humidity => Weather::YR::Model::Humidity->new(
166             from => $from,
167             to => $to,
168             lang => $self->lang,
169             percent => $loc->{humidity}->{value},
170             ),
171              
172             pressure => Weather::YR::Model::Pressure->new(
173             from => $from,
174             to => $to,
175             lang => $self->lang,
176             hPa => $loc->{pressure}->{value},
177             ),
178              
179             cloudiness => Weather::YR::Model::Cloudiness->new(
180             from => $from,
181             to => $to,
182             lang => $self->lang,
183             percent => $loc->{cloudiness}->{percent},
184             ),
185              
186             fog => Weather::YR::Model::Fog->new(
187             from => $from,
188             to => $to,
189             lang => $self->lang,
190             percent => $loc->{fog}->{percent},
191             ),
192              
193             dew_point_temperature => Weather::YR::Model::DewPointTemperature->new(
194             from => $from,
195             to => $to,
196             lang => $self->lang,
197             celsius => $loc->{dewpointTemperature}->{value},
198             ),
199              
200             temperature_probability => Weather::YR::Model::Probability::Temperature->new(
201             from => $from,
202             to => $to,
203             lang => $self->lang,
204             value => $loc->{temperatureProbability}->{value},
205             ),
206              
207             wind_probability => Weather::YR::Model::Probability::Wind->new(
208             from => $from,
209             to => $to,
210             lang => $self->lang,
211             value => $loc->{windProbability}->{value},
212 84         2696 ),
213             );
214             }
215             elsif ( my $p = $t->{location}->{precipitation} ) {
216             my $precipitation = Weather::YR::Model::Precipitation->new(
217             from => $from,
218             to => $to,
219             lang => $self->lang,
220             value => $p->{value},
221             min => $p->{minvalue},
222             max => $p->{maxvalue},
223             symbol => Weather::YR::Model::Precipitation::Symbol->new(
224             from => $from,
225             to => $to,
226             lang => $self->lang,
227             id => $t->{location}->{symbol}->{id},
228             number => $t->{location}->{symbol}->{number},
229 258         8223 ),
230             );
231              
232 258         982 $datapoint->add_precipitation( $precipitation );
233             }
234             }
235             }
236             else {
237 0         0 warn "No XML to generate forecast from!";
238             }
239              
240 1         34 return \@datapoints;
241             }
242              
243             =head2 days
244              
245             Returns an array reference of L<Weather::YR::LocationForecast::Day> instances.
246              
247             =cut
248              
249             sub _build_days {
250 1     1   3 my $self = shift;
251              
252 1         3 my %day_datapoints = ();
253              
254 1         2 foreach my $datapoint ( @{$self->datapoints} ) {
  1         34  
255 83         934 push( @{$day_datapoints{$datapoint->{from}->ymd}}, $datapoint );
  83         225  
256             }
257              
258 1         13 my @days = ();
259              
260 1         13 foreach my $date ( sort keys %day_datapoints ) {
261             my $day = Weather::YR::LocationForecast::Day->new(
262             date => DateTime::Format::ISO8601->parse_datetime( $date ),
263 10         33 datapoints => $day_datapoints{ $date },
264             );
265              
266 10         26 push( @days, $day );
267             }
268              
269 1         37 return \@days;
270             }
271              
272             =head2 now
273              
274             Returns a L<Weather::YR::LocationForecast::Day> instance, representing the
275             closest forecast in time.
276              
277             =cut
278              
279             sub _build_now {
280 0     0   0 my $self = shift;
281              
282 0         0 my $now = DateTime->now( time_zone => 'UTC' );
283 0         0 my $closest_dp = undef;
284              
285 0         0 foreach my $dp ( @{$self->today->datapoints} ) {
  0         0  
286 0 0       0 unless ( defined $closest_dp ) {
287 0         0 $closest_dp = $dp;
288 0         0 next;
289             }
290              
291 0         0 my $diff_from_now = abs( $dp->from->epoch - $now->epoch );
292              
293 0 0       0 if ( $diff_from_now < ( abs($closest_dp->from->epoch - $now->epoch) ) ) {
294 0         0 $closest_dp = $dp;
295             }
296             }
297              
298 0         0 return Weather::YR::LocationForecast::Day->new(
299             date => $closest_dp->from,
300             datapoints => [ $closest_dp ],
301             );
302             }
303              
304             =head2 today
305              
306             Returns a L<Weather::YR::LocationForecast::Day> instance, representing today's
307             weather.
308              
309             =cut
310              
311             sub _build_today {
312 1     1   3 my $self = shift;
313              
314 1         34 return $self->days->[0];
315             }
316              
317             =head2 tomorrow
318              
319             Returns a L<Weather::YR::LocationForecast::Day> instance, representing
320             tomorrow's weather.
321              
322             =cut
323              
324             sub _build_tomorrow {
325 0     0     my $self = shift;
326              
327 0           return $self->days->[1];
328             }
329              
330             __PACKAGE__->meta->make_immutable;
331              
332             1;