File Coverage

blib/lib/Geo/TAF.pm
Criterion Covered Total %
statement 221 314 70.3
branch 81 170 47.6
condition 34 122 27.8
subroutine 43 55 78.1
pod 12 12 100.0
total 391 673 58.1


line stmt bran cond sub pod time code
1             #
2             # A set of routine for decode TAF and METAR a bit better and more comprehensively
3             # than some other products I tried.
4             #
5             # $Id: TAF.pm,v 1.1.2.4 2003/02/03 17:26:37 minima Exp $
6             #
7             # Copyright (c) 2003 Dirk Koopman G1TLH
8             #
9              
10             package Geo::TAF;
11              
12 1     1   35239 use 5.005;
  1         5  
  1         41  
13 1     1   6 use strict;
  1         2  
  1         41  
14 1     1   6 use vars qw($VERSION);
  1         7  
  1         3212  
15              
16             $VERSION = '1.04';
17              
18              
19             my %err = (
20             '1' => "No valid ICAO designator",
21             '2' => "Length is less than 10 characters",
22             '3' => "No valid issue time",
23             '4' => "Expecting METAR or TAF at the beginning",
24             );
25              
26             my %clt = (
27             SKC => 1,
28             CLR => 1,
29             NSC => 1,
30             BLU => 1,
31             WHT => 1,
32             GRN => 1,
33             YLO => 1,
34             AMB => 1,
35             RED => 1,
36             BKN => 1,
37             NIL => 1,
38             );
39              
40             my %ignore = (
41             AUTO => 1,
42             COR => 1,
43             );
44              
45            
46             # Preloaded methods go here.
47              
48             sub new
49             {
50 2     2 1 89 my $pkg = shift;
51 2         9 my $self = bless {@_}, $pkg;
52 2   50     18 $self->{chunk_package} ||= "Geo::TAF::EN";
53 2         9 return $self;
54             }
55              
56             sub metar
57             {
58 1     1 1 3 my $self = shift;
59 1         3 my $l = shift;
60 1 50       5 return 2 unless length $l > 10;
61 1 50       18 $l = 'METAR ' . $l unless $l =~ /^\s*(?:METAR|TAF)\s/i;
62 1         5 return $self->decode($l);
63             }
64              
65             sub taf
66             {
67 1     1 1 2 my $self = shift;
68 1         2 my $l = shift;
69 1 50       4 return 2 unless length $l > 10;
70 1 50       7 $l = 'TAF ' . $l unless $l =~ /^\s*(?:METAR|TAF)\s/i;
71 1         4 return $self->decode($l);
72             }
73              
74             sub as_string
75             {
76 1     1 1 2 my $self = shift;
77 1         3 return join ' ', $self->as_strings;
78             }
79              
80             sub as_strings
81             {
82 1     1 1 2 my $self = shift;
83 1         8 my @out;
84 1         2 for (@{$self->{chunks}}) {
  1         3  
85 8         37 push @out, $_->as_string;
86             }
87 1         8 return @out;
88             }
89              
90             sub chunks
91             {
92 1     1 1 2 my $self = shift;
93 1 50       3 return exists $self->{chunks} ? @{$self->{chunks}} : ();
  1         5  
94             }
95              
96             sub as_chunk_strings
97             {
98 1     1 1 2 my $self = shift;
99 1         1 my @out;
100            
101 1         2 for (@{$self->{chunks}}) {
  1         3  
102 16         62 push @out, $_->as_chunk;
103             }
104 1         9 return @out;
105             }
106              
107             sub as_chunk_string
108             {
109 1     1 1 2 my $self = shift;
110 1         3 return join ' ', $self->as_chunk_strings;
111             }
112              
113             sub raw
114             {
115 0     0 1 0 return shift->{line};
116             }
117              
118             sub is_weather
119             {
120 0     0 1 0 return $_[0] =~ /^\s*(?:(?:METAR|TAF)\s+)?[A-Z]{4}\s+\d{6}Z?\s+/;
121             }
122              
123             sub errorp
124             {
125 0     0 1 0 my $self = shift;
126 0         0 my $code = shift;
127 0         0 return $err{"$code"};
128             }
129              
130             # basically all metars and tafs are the same, except that a metar is short
131             # and a taf can have many repeated sections for different times of the day
132             sub decode
133             {
134 2     2 1 4 my $self = shift;
135 2         6 my $l = uc shift;
136              
137 2         6 $l =~ s/=$//;
138            
139 2         17 my @tok = split /\s+/, $l;
140              
141 2         14 $self->{line} = join ' ', @tok;
142            
143            
144             # do we explicitly have a METAR or a TAF
145 2         4 my $t = shift @tok;
146 2 100       9 if ($t eq 'TAF') {
    50          
147 1         3 $self->{taf} = 1;
148             } elsif ($t eq 'METAR') {
149 1         2 $self->{taf} = 0;
150             } else {
151 0         0 return 4;
152             }
153              
154             # next token is the ICAO dseignator
155 2         4 $t = shift @tok;
156 2 50       10 if ($t =~ /^[A-Z]{4}$/) {
157 2         6 $self->{icao} = $t;
158             } else {
159 0         0 return 1;
160             }
161              
162             # next token is an issue time
163 2         3 $t = shift @tok;
164 2 50       15 if (my ($day, $time) = $t =~ /^(\d\d)(\d{4})Z?$/) {
165 2         5 $self->{day} = $day;
166 2         6 $self->{time} = _time($time);
167             } else {
168 0         0 return 3;
169             }
170              
171             # if it is a TAF then expect a validity (may be missing)
172 2 100       9 if ($self->{taf}) {
173 1 50       7 if (my ($vd, $vfrom, $vto) = $tok[0] =~ /^(\d\d)(\d\d)(\d\d)$/) {
174 1         2 $self->{valid_day} = $vd;
175 1         3 $self->{valid_from} = _time($vfrom * 100);
176 1         4 $self->{valid_to} = _time($vto * 100);
177 1         3 shift @tok;
178             }
179             }
180              
181             # we are now into the 'list' of things that can repeat over and over
182              
183 2 100       13 my @chunk = (
184             $self->_chunk('HEAD', $self->{taf} ? 'TAF' : 'METAR',
185             $self->{icao}, $self->{day}, $self->{time})
186             );
187            
188 2 100       11 push @chunk, $self->_chunk('VALID', $self->{valid_day}, $self->{valid_from},
189             $self->{valid_to}) if $self->{valid_day};
190              
191 2         6 while (@tok) {
192 21         64 $t = shift @tok;
193            
194             # temporary
195 21 100 100     1019 if ($t eq 'TEMPO' || $t eq 'BECMG') {
    50 33        
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    100          
    100          
    50          
196            
197             # next token may be a time if it is a taf
198 3         4 my ($from, $to);
199 3 50 33     22 if (@tok && (($from, $to) = $tok[0] =~ /^(\d\d)(\d\d)$/)) {
200 3 50 33     40 if ($self->{taf} && $from >= 0 && $from <= 24 && $to >= 0 && $to <= 24) {
      33        
      33        
      33        
201 3         10 shift @tok;
202 3         11 $from = _time($from * 100);
203 3         7 $to = _time($to * 100);
204             } else {
205 0         0 undef $from;
206 0         0 undef $to;
207             }
208             }
209 3         8 push @chunk, $self->_chunk($t, $from, $to);
210              
211             # ignore
212             } elsif ($ignore{$t}) {
213             ;
214            
215             # no sig weather
216             } elsif ($t eq 'NOSIG' || $t eq 'NSW') {
217 0         0 push @chunk, $self->_chunk('WEATHER', 'NOSIG');
218              
219             # specific broken on its own
220             } elsif ($t eq 'BKN') {
221 0         0 push @chunk, $self->_chunk('WEATHER', $t);
222            
223             # other 3 letter codes
224             } elsif ($clt{$t}) {
225 0         0 push @chunk, $self->_chunk('CLOUD', $t);
226            
227             # EU CAVOK viz > 10000m, no cloud, no significant weather
228             } elsif ($t eq 'CAVOK') {
229 0   0     0 $self->{viz_dist} ||= ">10000";
230 0   0     0 $self->{viz_units} ||= 'm';
231 0         0 push @chunk, $self->_chunk('CLOUD', 'CAVOK');
232              
233             # RMK group (end for now)
234             } elsif ($t eq 'RMK') {
235 0         0 last;
236              
237             # from
238             } elsif (my ($time) = $t =~ /^FM(\d\d\d\d)$/ ) {
239 0         0 push @chunk, $self->_chunk('FROM', _time($time));
240              
241             # Until
242             } elsif (($time) = $t =~ /^TL(\d\d\d\d)$/ ) {
243 0         0 push @chunk, $self->_chunk('TIL', _time($time));
244              
245             # probability
246             } elsif (my ($percent) = $t =~ /^PROB(\d\d)$/ ) {
247              
248             # next token may be a time if it is a taf
249 1         2 my ($from, $to);
250 1 50 33     16 if (@tok && (($from, $to) = $tok[0] =~ /^(\d\d)(\d\d)$/)) {
251 0 0 0     0 if ($self->{taf} && $from >= 0 && $from <= 24 && $to >= 0 && $to <= 24) {
      0        
      0        
      0        
252 0         0 shift @tok;
253 0         0 $from = _time($from * 100);
254 0         0 $to = _time($to * 100);
255             } else {
256 0         0 undef $from;
257 0         0 undef $to;
258             }
259             }
260 1         2 push @chunk, $self->_chunk('PROB', $percent, $from, $to);
261              
262             # runway
263             } elsif (my ($sort, $dir) = $t =~ /^(RWY?|LDG)(\d\d[RLC]?)$/ ) {
264 0         0 push @chunk, $self->_chunk('RWY', $sort, $dir);
265              
266             # a wind group
267             } elsif (my ($wdir, $spd, $gust, $unit) = $t =~ /^(\d\d\d|VRB)(\d\d)(?:G(\d\d))?(KT|MPH|MPS|KMH)$/) {
268            
269 3         4 my ($fromdir, $todir);
270            
271 3 50 66     13 if (@tok && (($fromdir, $todir) = $tok[0] =~ /^(\d\d\d)V(\d\d\d)$/)) {
272 0         0 shift @tok;
273             }
274            
275             # it could be variable so look at the next token
276              
277 3         6 $spd = 0 + $spd;
278 3 50       6 $gust = 0 + $gust if defined $gust;
279 3         9 $unit = ucfirst lc $unit;
280 3 50       7 $unit = 'm/sec' if $unit eq 'Mps';
281 3   66     12 $self->{wind_dir} ||= $wdir;
282 3   66     13 $self->{wind_speed} ||= $spd;
283 3   33     12 $self->{wind_gusting} ||= $gust;
284 3   66     10 $self->{wind_units} ||= $unit;
285 3         9 push @chunk, $self->_chunk('WIND', $wdir, $spd, $gust, $unit, $fromdir, $todir);
286            
287             # pressure
288             } elsif (my ($u, $p, $punit) = $t =~ /^([QA])(?:NH)?(\d\d\d\d)(INS?)?$/) {
289              
290 1         2 $p = 0 + $p;
291 1 50 33     7 if ($u eq 'A' || $punit && $punit =~ /^I/) {
      33        
292 0         0 $p = sprintf "%.2f", $p / 100;
293 0         0 $u = 'in';
294             } else {
295 1         1 $u = 'hPa';
296             }
297 1   33     7 $self->{pressure} ||= $p;
298 1   33     7 $self->{pressure_units} ||= $u;
299 1         3 push @chunk, $self->_chunk('PRESS', $p, $u);
300              
301             # viz group in metres
302             } elsif (my ($viz, $mist) = $t =~ m!^(\d\d\d\d[NSEW]{0,2})([A-Z][A-Z])?$!) {
303 4 100       10 $viz = $viz eq '9999' ? ">10000" : 0 + $viz;
304 4   66     13 $self->{viz_dist} ||= $viz;
305 4   100     11 $self->{viz_units} ||= 'm';
306 4         10 push @chunk, $self->_chunk('VIZ', $viz, 'm');
307 4 50       17 push @chunk, $self->_chunk('WEATHER', $mist) if $mist;
308              
309             # viz group in KM
310             } elsif (($viz) = $t =~ m!^(\d+)KM$!) {
311 0 0       0 $viz = $viz eq '9999' ? ">10000" : 0 + $viz;
312 0   0     0 $self->{viz_dist} ||= $viz;
313 0   0     0 $self->{viz_units} ||= 'Km';
314 0         0 push @chunk, $self->_chunk('VIZ', $viz, 'Km');
315              
316             # viz group in miles and faction of a mile with space between
317             } elsif (my ($m) = $t =~ m!^(\d)$!) {
318 0         0 my $viz;
319 0 0 0     0 if (@tok && (($viz) = $tok[0] =~ m!^(\d/\d)SM$!)) {
320 0         0 shift @tok;
321 0         0 $viz = "$m $viz";
322 0   0     0 $self->{viz_dist} ||= $viz;
323 0   0     0 $self->{viz_units} ||= 'miles';
324 0         0 push @chunk, $self->_chunk('VIZ', $viz, 'miles');
325             }
326            
327             # viz group in miles (either in miles or under a mile)
328             } elsif (my ($lt, $mviz) = $t =~ m!^(M)?(\d+(:?/\d)?)SM$!) {
329 0 0       0 $mviz = '<' . $mviz if $lt;
330 0   0     0 $self->{viz_dist} ||= $mviz;
331 0   0     0 $self->{viz_units} ||= 'Stat. Miles';
332 0         0 push @chunk, $self->_chunk('VIZ', $mviz, 'Miles');
333            
334              
335             # runway visual range
336             } elsif (my ($rw, $rlt, $range, $vlt, $var, $runit, $tend) = $t =~ m!^R(\d\d[LRC]?)/([MP])?(\d\d\d\d)(?:V([MP])(\d\d\d\d))?(?:(FT)/?)?([UND])?$!) {
337 0 0       0 $runit = 'm' unless $runit;
338 0         0 $runit = lc $unit;
339 0 0 0     0 $range = "<$range" if $rlt && $rlt eq 'M';
340 0 0 0     0 $range = ">$range" if $rlt && $rlt eq 'P';
341 0 0 0     0 $var = "<$var" if $vlt && $vlt eq 'M';
342 0 0 0     0 $var = ">$var" if $vlt && $vlt eq 'P';
343 0         0 push @chunk, $self->_chunk('RVR', $rw, $range, $var, $runit, $tend);
344            
345             # weather
346             } elsif (my ($deg, $w) = $t =~ /^(\+|\-|VC)?([A-Z][A-Z]{1,4})$/) {
347 3         18 push @chunk, $self->_chunk('WEATHER', $deg, $w =~ /([A-Z][A-Z])/g);
348            
349             # cloud and stuff
350             } elsif (my ($amt, $height, $cb) = $t =~ m!^(FEW|SCT|BKN|OVC|SKC|CLR|VV|///)(\d\d\d|///)(CB|TCU)?$!) {
351 5 100 33     27 push @chunk, $self->_chunk('CLOUD', $amt, $height eq '///' ? 0 : $height * 100, $cb) unless $amt eq '///' && $height eq '///';
    50          
352              
353             # temp / dew point
354             } elsif (my ($ms, $t, $n, $d) = $t =~ m!^(M)?(\d\d)/(M)?(\d\d)?$!) {
355 1         3 $t = 0 + $t;
356 1         2 $d = 0 + $d;
357 1 50       3 $t = -$t if defined $ms;
358 1 50 33     9 $d = -$d if defined $d && defined $n;
359 1   33     6 $self->{temp} ||= $t;
360 1   33     5 $self->{dewpoint} ||= $d;
361 1         3 push @chunk, $self->_chunk('TEMP', $t, $d);
362             }
363            
364             }
365 2         7 $self->{chunks} = \@chunk;
366 2         12 return undef;
367             }
368              
369             sub _chunk
370             {
371 24     24   101 my $self = shift;
372 24         26 my $pkg = shift;
373 1     1   9 no strict 'refs';
  1         2  
  1         117  
374 24         47 $pkg = $self->{chunk_package} . '::' . $pkg;
375 24         127 return $pkg->new(@_);
376             }
377              
378             sub _time
379             {
380 10     10   61 return sprintf "%02d:%02d", unpack "a2a2", sprintf "%04d", shift;
381             }
382              
383             # accessors
384             sub AUTOLOAD
385             {
386 1     1   5 no strict;
  1         1  
  1         473  
387 8     8   46 my ($package, $name) = $AUTOLOAD =~ /^(.*)::(\w+)$/;
388 8 50       20 return if $name eq 'DESTROY';
389              
390 8     8   38 *$AUTOLOAD = sub {return $_[0]->{$name}};
  8         67  
391 8         27 goto &$AUTOLOAD;
392             }
393              
394             #
395             # these are the translation packages
396             #
397             # First the factory method
398             #
399              
400             package Geo::TAF::EN;
401              
402             sub new
403             {
404 24     24   32 my $pkg = shift;
405 24         158 return bless [@_], $pkg;
406             }
407              
408             sub as_chunk
409             {
410 16     16   18 my $self = shift;
411 16         67 my ($n) = (ref $self) =~ /::(\w+)$/;
412 16 100       40 return '[' . join(' ', $n, map {defined $_ ? $_ : '?'} @$self) . ']';
  49         145  
413             }
414              
415             sub as_string
416             {
417 0     0   0 my $self = shift;
418 0         0 my ($n) = (ref $self) =~ /::(\w+)$/;
419 0 0       0 return join ' ', ucfirst $n, map {defined $_ ? $_ : ()} @$self;
  0         0  
420             }
421              
422             sub day
423             {
424 1     1   1 my $pkg = shift;
425 1 50       7 my $d = sprintf "%d", ref($pkg) ? shift : $pkg;
426 1 50       7 if ($d =~ /1$/) {
    0          
    0          
427 1         7 return "${d}st";
428             } elsif ($d =~ /2$/) {
429 0         0 return "${d}nd";
430             } elsif ($d =~ /3$/) {
431 0         0 return "${d}rd";
432             }
433 0         0 return "${d}th";
434             }
435              
436              
437             package Geo::TAF::EN::HEAD;
438 1     1   6 use vars qw(@ISA);
  1         2  
  1         132  
439             @ISA = qw(Geo::TAF::EN);
440              
441             sub as_string
442             {
443 1     1   2 my $self = shift;
444 1         17 return "$self->[0] for $self->[1] issued at $self->[3] on " . $self->day($self->[2]);
445             }
446              
447             package Geo::TAF::EN::VALID;
448 1     1   5 use vars qw(@ISA);
  1         2  
  1         108  
449             @ISA = qw(Geo::TAF::EN);
450              
451             sub as_string
452             {
453 0     0   0 my $self = shift;
454 0         0 return "valid from $self->[1] to $self->[2] on " . $self->day($self->[0]);
455             }
456              
457              
458             package Geo::TAF::EN::WIND;
459 1     1   6 use vars qw(@ISA);
  1         1  
  1         298  
460             @ISA = qw(Geo::TAF::EN);
461              
462             # direction, $speed, $gusts, $unit, $fromdir, $todir
463             sub as_string
464             {
465 1     1   3 my $self = shift;
466 1         2 my $out = "wind";
467 1 50       11 $out .= $self->[0] eq 'VRB' ? " variable" : " $self->[0]";
468 1 50       4 $out .= " varying between $self->[4] and $self->[5]" if defined $self->[4];
469 1 50       5 $out .= ($self->[0] eq 'VRB' ? '' : " degrees") . " at $self->[1]";
470 1 50       4 $out .= " gusting $self->[2]" if defined $self->[2];
471 1         1 $out .= $self->[3];
472 1         3 return $out;
473             }
474              
475             package Geo::TAF::EN::PRESS;
476 1     1   5 use vars qw(@ISA);
  1         2  
  1         109  
477             @ISA = qw(Geo::TAF::EN);
478              
479             # $pressure, $unit
480             sub as_string
481             {
482 1     1   2 my $self = shift;
483 1         9 return "QNH $self->[0]$self->[1]";
484             }
485              
486             # temperature, dewpoint
487             package Geo::TAF::EN::TEMP;
488 1     1   5 use vars qw(@ISA);
  1         2  
  1         125  
489             @ISA = qw(Geo::TAF::EN);
490              
491             sub as_string
492             {
493 1     1   2 my $self = shift;
494 1         38 my $out = "temperature $self->[0]C";
495 1 50       7 $out .= " dewpoint $self->[1]C" if defined $self->[1];
496              
497 1         3 return $out;
498             }
499              
500             package Geo::TAF::EN::CLOUD;
501 1     1   6 use vars qw(@ISA);
  1         2  
  1         230  
502             @ISA = qw(Geo::TAF::EN);
503              
504             my %st = (
505             VV => 'vertical visibility',
506             SKC => "no cloud",
507             CLR => "no cloud no significant weather",
508             SCT => "3-4 oktas",
509             BKN => "5-7 oktas",
510             FEW => "0-2 oktas",
511             OVC => "8 oktas overcast",
512             CAVOK => "no cloud below 5000ft >10Km visibility no significant weather (CAVOK)",
513             CB => 'thunder clouds',
514             TCU => 'towering cumulus',
515             NSC => 'no significant cloud',
516             BLU => '3 oktas at 2500ft 8Km visibility',
517             WHT => '3 oktas at 1500ft 5Km visibility',
518             GRN => '3 oktas at 700ft 3700m visibility',
519             YLO => '3 oktas at 300ft 1600m visibility',
520             AMB => '3 oktas at 200ft 800m visibility',
521             RED => '3 oktas at <200ft <800m visibility',
522             NIL => 'no weather',
523             '///' => 'some',
524             );
525              
526             sub as_string
527             {
528 2     2   3 my $self = shift;
529 2 50       11 return $st{$self->[0]} if @$self == 1;
530 2 50       13 return $st{$self->[0]} . " $self->[1]ft" if $self->[0] eq 'VV';
531 2 50       13 return $st{$self->[0]} . " cloud at $self->[1]ft" . ((defined $self->[2]) ? " with $st{$self->[2]}" : "");
532             }
533              
534             package Geo::TAF::EN::WEATHER;
535 1     1   5 use vars qw(@ISA);
  1         2  
  1         409  
536             @ISA = qw(Geo::TAF::EN);
537              
538             my %wt = (
539             '+' => 'heavy',
540             '-' => 'light',
541             'VC' => 'in the vicinity',
542              
543             MI => 'shallow',
544             PI => 'partial',
545             BC => 'patches of',
546             DR => 'low drifting',
547             BL => 'blowing',
548             SH => 'showers',
549             TS => 'thunderstorms containing',
550             FZ => 'freezing',
551             RE => 'recent',
552            
553             DZ => 'drizzle',
554             RA => 'rain',
555             SN => 'snow',
556             SG => 'snow grains',
557             IC => 'ice crystals',
558             PE => 'ice pellets',
559             GR => 'hail',
560             GS => 'small hail/snow pellets',
561             UP => 'unknown precip',
562            
563             BR => 'mist',
564             FG => 'fog',
565             FU => 'smoke',
566             VA => 'volcanic ash',
567             DU => 'dust',
568             SA => 'sand',
569             HZ => 'haze',
570             PY => 'spray',
571            
572             PO => 'dust/sand whirls',
573             SQ => 'squalls',
574             FC => 'tornado',
575             SS => 'sand storm',
576             DS => 'dust storm',
577             '+FC' => 'water spouts',
578             WS => 'wind shear',
579             'BKN' => 'broken',
580              
581             'NOSIG' => 'no significant weather',
582            
583             );
584              
585             sub as_string
586             {
587 1     1   2 my $self = shift;
588 1         2 my @out;
589              
590 1         1 my ($vic, $shower);
591 0         0 my @in;
592 1         6 push @in, @$self;
593            
594 1         4 while (@in) {
595 3         5 my $t = shift @in;
596              
597 3 100 33     22 if (!defined $t) {
    50          
    100          
    50          
598 1         3 next;
599             } elsif ($t eq 'VC') {
600 0         0 $vic++;
601 0         0 next;
602             } elsif ($t eq 'SH') {
603 1         2 $shower++;
604 1         3 next;
605             } elsif ($t eq '+' && $self->[0] eq 'FC') {
606 0         0 push @out, $wt{'+FC'};
607 0         0 shift;
608 0         0 next;
609             }
610            
611 1         3 push @out, $wt{$t};
612            
613 1 50 33     6 if (@out && $shower) {
614 1         2 $shower = 0;
615 1         4 push @out, $wt{'SH'};
616             }
617             }
618 1 50       3 push @out, $wt{'VC'} if $vic;
619              
620 1         4 return join ' ', @out;
621             }
622              
623             package Geo::TAF::EN::RVR;
624 1     1   6 use vars qw(@ISA);
  1         2  
  1         162  
625             @ISA = qw(Geo::TAF::EN);
626              
627             sub as_string
628             {
629 0     0   0 my $self = shift;
630 0         0 my $out = "visual range on runway $self->[0] is $self->[1]$self->[3]";
631 0 0       0 $out .= " varying to $self->[2]$self->[3]" if defined $self->[2];
632 0 0       0 if (defined $self->[4]) {
633 0 0       0 $out .= " decreasing" if $self->[4] eq 'D';
634 0 0       0 $out .= " increasing" if $self->[4] eq 'U';
635             }
636 0         0 return $out;
637             }
638              
639             package Geo::TAF::EN::RWY;
640 1     1   18 use vars qw(@ISA);
  1         2  
  1         105  
641             @ISA = qw(Geo::TAF::EN);
642              
643             sub as_string
644             {
645 0     0   0 my $self = shift;
646 0 0       0 my $out = $self->[0] eq 'LDG' ? "landing " : '';
647 0         0 $out .= "runway $self->[1]";
648 0         0 return $out;
649             }
650              
651             package Geo::TAF::EN::PROB;
652 1     1   6 use vars qw(@ISA);
  1         1  
  1         140  
653             @ISA = qw(Geo::TAF::EN);
654              
655             sub as_string
656             {
657 0     0   0 my $self = shift;
658            
659 0         0 my $out = "probability $self->[0]%";
660 0 0       0 $out .= " $self->[1] to $self->[2]" if defined $self->[1];
661 0         0 return $out;
662             }
663              
664             package Geo::TAF::EN::TEMPO;
665 1     1   5 use vars qw(@ISA);
  1         2  
  1         116  
666             @ISA = qw(Geo::TAF::EN);
667              
668             sub as_string
669             {
670 0     0   0 my $self = shift;
671 0         0 my $out = "temporarily";
672 0 0       0 $out .= " $self->[0] to $self->[1]" if defined $self->[0];
673              
674 0         0 return $out;
675             }
676              
677             package Geo::TAF::EN::BECMG;
678 1     1   5 use vars qw(@ISA);
  1         1  
  1         122  
679             @ISA = qw(Geo::TAF::EN);
680              
681             sub as_string
682             {
683 0     0   0 my $self = shift;
684 0         0 my $out = "becoming";
685 0 0       0 $out .= " $self->[0] to $self->[1]" if defined $self->[0];
686              
687 0         0 return $out;
688             }
689              
690             package Geo::TAF::EN::VIZ;
691 1     1   5 use vars qw(@ISA);
  1         1  
  1         93  
692             @ISA = qw(Geo::TAF::EN);
693              
694             sub as_string
695             {
696 1     1   2 my $self = shift;
697              
698 1         8 return "visibility $self->[0]$self->[1]";
699             }
700              
701             package Geo::TAF::EN::FROM;
702 1     1   4 use vars qw(@ISA);
  1         7  
  1         105  
703             @ISA = qw(Geo::TAF::EN);
704              
705             sub as_string
706             {
707 0     0     my $self = shift;
708              
709 0           return "from $self->[0]";
710             }
711              
712             package Geo::TAF::EN::TIL;
713 1     1   5 use vars qw(@ISA);
  1         2  
  1         112  
714             @ISA = qw(Geo::TAF::EN);
715              
716             sub as_string
717             {
718 0     0     my $self = shift;
719              
720 0           return "until $self->[0]";
721             }
722              
723              
724             # Autoload methods go after =cut, and are processed by the autosplit program.
725              
726             1;
727             __END__