File Coverage

blib/lib/Geo/METAR.pm
Criterion Covered Total %
statement 217 471 46.0
branch 82 250 32.8
condition 107 183 58.4
subroutine 9 13 69.2
pod 4 6 66.6
total 419 923 45.4


line stmt bran cond sub pod time code
1             # $Id: METAR.pm,v 1.11 2008/01/02 13:47:00 koos Exp $
2              
3             # KH: fix the parser
4             # should be a finite state machine
5             # - metar has rules what comes after what. but codes can be missing.
6             # (measurement not done) or //// (measurement broken at the moment)
7             # so given a state counter, it can stay the same or go up one or more states,
8             # but it can never go down
9             #
10             # info on the last bit which is actually a forecast: (German)
11             # http://www.wetterklima.de/flug/metar/Metarvorhersage.htm
12             #
13             # more info here (dutch, and txt 707 is not standard metar)
14             # http://www.vwkweb.nl/index.html?http://www.vwkweb.nl/weerinfo/weerinfo_teletekst707.html
15             # and also (dutch)
16             # http://www.gids.nl/weather/eheh/metari.html
17             #
18             # 'METAR decoding in Europe'
19             # http://users.hol.gr/~chatos/VATSIM/TM/metar.html
20             #
21             # english explanation
22             # http://booty.org.uk/booty.weather/metinfo/codes/METAR_decode.htm
23             #
24             # canadian explanation
25             # http://meteocentre.com/doc/metar.html
26             #
27             # 'METAR decoding, TAF decoding'
28             # http://stoivane.kapsi.fi/metar/
29             #
30              
31             # This module is used for decoding NWS METAR code.
32              
33             # Example METARs
34             #
35             # Findlay, Ohio
36             # KFDY 251450Z 21012G21KT 8SM OVC065 04/M01 A3010 RMK SLP201 57014
37             #
38             # Toledo, Ohio
39             # KTOL 251451Z 23016G22KT 8SM CLR 04/00 A3006 RMK AO2 SLP185 T00440000 56016
40             #
41             # Cleveland, Ohio
42             # KCLE 251554Z 20015KT 10SM FEW055 OVC070 03/M02 A3011 RMK AO2 SLP205 T00331017
43             #
44             # Houston, Texas
45             # KHST 251455Z 06017G22KT 7SM FEW040 BKN330 25/18 A3016 RMK SLP213 8/508
46             # 9/205 51007
47             #
48             # LA
49             #
50             # KLAX 251450Z 07004KT 7SM SCT100 BKN200 14/11 A3005 RMK AO2 SLP173
51             # T01390111 56005
52             #
53             # Soesterberg
54             #
55             # EHSB 181325Z 24009KT 8000 -RA BR FEW011 SCT022 OVC030 07/06 Q1011 WHT WHT TEMPO GRN
56              
57             # For METAR info, please see
58             # http://tgsv5.nws.noaa.gov/oso/oso1/oso12/metar.htm
59             # moved
60             # http://metar.noaa.gov/
61             #
62             # in scary detail (metar coding)
63             #
64             # http://metar.noaa.gov/table_master.jsp?sub_menu=yes&show=fmh1ch12.htm&dir=./handbook/&title=title_handbook
65             #
66              
67              
68             # The METAR specification is dictated in the Federal Meteorological Handbook
69             # which is available on-line at:
70             # http://tgsv5.nws.noaa.gov/oso/oso1/oso12/fmh1.htm
71              
72             # General Structure is:
73             # TYPE, SITE, DATE/TIME, WIND, VISIBILITY, CLOUDS, TEMPERATURE, PRESSURE, REMARKS
74              
75             # Specifically:
76              
77             # TYPE (optional)
78             # METAR or SPECI
79             # METAR: regular report
80             # SPECI: special report
81              
82             # SITE (required, only once)
83             #
84             # 4-Char site identifier (KLAX for LA, KHST for Houston)
85              
86             # DATE/TIME (required, only once)
87             #
88             # 6-digit time followed by "Z", indicating UTC
89              
90             # REPORT MODIFIER (optional)
91             # AUTO or COR
92             # AUTO = Automatic report (no human intervention)
93             # COR = Corrected METAR or SPECI
94              
95             # WIND (group)
96             #
97             # Wind direction (\d\d\d) and speed (\d?\d\d) and optionaling gusting
98             # information denoted by "G" and speed (\d?\d\d) followed by "KT", for knots.
99             #
100             # Wind direction MAY be "VRB" (variable) instead of a compass direction.
101             #
102             # Variable Wind Direction (Speeds greater than 6 knots). Variable wind
103             # direction with wind speed greater than 6 knots shall be coded in the
104             # format, dndndnVdxdxdx
105             #
106             # Calm wind is recorded as 00000KT.
107              
108             # VISIBILITY (group)
109             #
110             # Visibility (\d+) followed by "SM" for statute miles or no 'SM' for meters
111             # (european)
112             #
113             # May be 1/(\d)SM for a fraction.
114             #
115             # May be M1/\d)SM for less than a given fraction. (M="-")
116             #
117             # \d\d\d\d according to KNMI
118             # lowest horizontal visibility (looking around)
119             # round down
120             # 0000 - 0500m in steps of 0050m
121             # 0500 - 5000m in steps of 0100m
122             # 5000 - 9999m in steps of 1000m
123             # 10km or more is 9999
124              
125             # RUNWAY Visual Range (Group)
126             #
127             # R(\d\d\d)(L|C|R)?/((M|P)?\d\d\d\d){1,2}FT
128             #
129             # Where:
130             # $1 is the runway number.
131             # $2 is the runway (Left/Center/Right) for parallel runways.
132             # $3 is the reported visibility in feet.
133             # $4 is the MAXIMUM reported visibility, making $3 the MINIMUM.
134             #
135             # "M" beginning a value means less than the reportable value of \d\d\d\d.
136             # "P" beginning a value means more than the reportable value of \d\d\d\d.
137             #
138             # new
139             #
140             # R(\d\d\d[LCR]?)/([MP]?\d\d\d\d)(V[MP]?\d\d\d\d)?FT
141             #
142             # $1 runway number + Left/Center/Right
143             # $2 visibility feet
144             # $3 Varying feet
145             # M = less than
146             # P = more than
147              
148             # WEATHER (Present Weather Group)
149             #
150             # See table in Chapter 12 of FMH-1.
151              
152             # CLOUDS (Sky Condition Group)
153             #
154             # A space-separated grouping of cloud conditions which will contain at least
155             # one cloud report. Examples: "CLR", "BKN330", "SCT100", "FEW055", "OVC070"
156             # The three-letter codes represent the condition (Clear, Broken, Scattered,
157             # Few, Overcast) and the numbers (\d\d\d) represent altitlude/100.
158             #
159             # The report may have a trailing CB (cumulonimbus) or TCU (towering
160             # cumulus) appended. ([A-Z]{2,3})?(\d\d\d)(CB|TCU)?
161              
162             # Vertical visibility (VV)
163             #
164             # VV
165             # This group is reported when the sky is obscured. VV is the group indicator,
166             # and hshshs is the vertical visibility in units of 30 metres
167             # (hundreds of feet).
168             #
169             # hshshs - Examples of Encoding
170             # HEIGHT METAR CODE
171             # 100 ft (30 metres) 001
172             # 450 ft (135 metres) 004
173             # 2,700 ft (810 metres) 027
174             # 12,600 ft (3,780 metres) 1300
175             #
176             # source http://meteocentre.com/doc/metar.html
177             #
178             # TEMPERATURE and DEW POINT
179             #
180             # (M?\d\d)/(M?\d\d) where $1 is the current temperature in degrees celcius,
181             # and $2 is the current dewpoint in degrees celcius.
182             #
183             # The "M" signifies a negative temperature, so converting the "M" to a
184             # "-" ought to suffice.
185              
186             # PRESSURE
187             #
188             # The pressure, or altimeter setting, at the reporting site recorded in
189             # inches of mercury (Hg) minus the decimal point. It should always look
190             # like (A\d\d\d\d).
191             #
192             # KNMI: Q\d\d\d\d pressure in hPa calculated for sea level
193              
194             # REMARKS
195             #
196             # Remarks contain additional information. They are optional but often
197             # informative of special conditions.
198             #
199             # Remarks begin with the "RMK" keyword and continue to the end of the line.
200             #
201             # trend group
202             #
203             # color codes BLU WHT GRN YLO AMB RED
204             # BLACK: vliegveld dicht
205             # future trend
206             # NOSIG no significant change
207             # TEMPO temporary change
208             # WHT WHT TEMPO GRN = current white, prediction white temporary green
209             # NSW no significant weather
210             # AT at a given time
211             # PROB30 probability 30%
212             # BECMG becoming
213             # BECMG (weather) FM \d\d\d\d TL \d\d\d\d = from until utc times
214             # BECMG (weather) AT \d\d\d\d = at utc time
215             # BECMG (weather) TL \d\d\d\d = change until utc time
216             # BECMG 2000 visibility
217             # BECMG NSW weather type
218             # etc etc
219             # FCST CANCEL (2 tokens!) Forecast cancel: no further forecasts for a while
220              
221             ### Package Definition
222              
223             package Geo::METAR;
224              
225             ## Required Modules
226              
227 1     1   39182 use 5.005;
  1         4  
  1         47  
228 1     1   8 use strict;
  1         2  
  1         42  
229 1     1   6 use vars qw($AUTOLOAD $VERSION);
  1         9  
  1         74  
230 1     1   5 use Carp 'cluck';
  1         1  
  1         6675  
231              
232             $VERSION = '1.15';
233              
234             ##
235             ## Lookup tables
236             ##
237              
238             my %_weather_types = (
239             MI => 'shallow',
240             PI => 'partial',
241             BC => 'patches',
242             DR => 'drizzle',
243             BL => 'blowing',
244             SH => 'shower(s)',
245             TS => 'thunderstorm',
246             FZ => 'freezing',
247              
248             DZ => 'drizzle',
249             RA => 'rain',
250             SN => 'snow',
251             SG => 'snow grains',
252             IC => 'ice crystals',
253             PE => 'ice pellets',
254             GR => 'hail',
255             GS => 'small hail/snow pellets',
256             UP => 'unknown precip',
257              
258             BR => 'mist',
259             FG => 'fog',
260             PRFG => 'fog banks', # officially PR is a modifier of FG
261             FU => 'smoke',
262             VA => 'volcanic ash',
263             DU => 'dust',
264             SA => 'sand',
265             HZ => 'haze',
266             PY => 'spray',
267              
268             PO => 'dust/sand whirls',
269             SQ => 'squalls',
270             FC => 'funnel cloud(tornado/waterspout)',
271             SS => 'sand storm',
272             DS => 'dust storm',
273             );
274              
275             my $_weather_types_pat = join("|", keys(%_weather_types));
276              
277             my %_sky_types = (
278             SKC => "Sky Clear",
279             CLR => "Sky Clear",
280             SCT => "Scattered",
281             BKN => "Broken",
282             FEW => "Few",
283             OVC => "Solid Overcast",
284             NSC => "No significant clouds",
285             NCD => "No cloud detected",
286             );
287              
288             my %_trend_types = (
289             BLU => "8 km view",
290             WHT => "5 km view",
291             GRN => "3.7 km view",
292             YLO => "1.6 km view",
293             AMB => "0.8 km view",
294             RED => "< 0.8 km view",
295             BLACK => "airport closed",
296             NOSIG => "No significant change",
297             TEMPO => "Temporary change",
298             NSW => "No significant weather",
299             PROB => "Probability",
300             BECMG => "Becoming",
301             LAST => "Last",
302             );
303              
304             my $_trend_types_pat = join("|", keys(%_trend_types));
305              
306             ##
307             ## Constructor.
308             ##
309              
310             sub new
311             {
312 1     1 0 46 my $this = shift;
313 1   33     14 my $class = ref($this) || $this;
314 1         3 my $self = {};
315              
316             ##
317             ## UPPERCASE items have documented accssor functions (methods) or
318             ## use AUTOLOAD, while lowercase items are reserved for internal
319             ## use.
320             ##
321              
322 1         5 $self->{VERSION} = $VERSION; # version number
323 1         2 $self->{METAR} = undef; # the actual, raw METAR
324 1         2 $self->{TYPE} = undef; # the type of report
325 1         3 $self->{SITE} = undef; # site code
326 1         3 $self->{DATE} = undef; # when it was issued
327 1         4 $self->{TIME} = undef; # time it was issued
328 1         3 $self->{MOD} = undef; # modifier (AUTO/COR)
329 1         3 $self->{WIND_DIR_DEG} = undef; # wind dir in degrees
330 1         44 $self->{WIND_DIR_ENG} = undef; # wind dir in english (Northwest/Southeast)
331 1         3 $self->{WIND_DIR_ABB} = undef; # wind dir in abbreviated english (NW/SE)
332 1         3 $self->{WIND_KTS} = undef; # wind speed (knots)
333 1         3 $self->{WIND_GUST_KTS} = undef; # wind gusts (knots)
334 1         3 $self->{WIND_MPH} = undef; # wind speed (MPH)
335 1         3 $self->{WIND_GUST_MPH} = undef; # wind gusts (MPH)
336 1         4 $self->{WIND_VAR} = undef; # wind variation (text)
337 1         3 $self->{WIND_VAR_1} = undef; # wind variation (direction 1)
338 1         3 $self->{WIND_VAR_2} = undef; # wind variation (direction 2)
339 1         3 $self->{VISIBILITY} = undef; # visibility info
340 1         3 $self->{RUNWAY} = [ ]; # runway vis.
341 1         2 $self->{WEATHER} = [ ]; # current weather
342 1         3 $self->{WEATHER_LOG} = [ ]; # weather log
343 1         3 $self->{SKY} = [ ]; # current sky (cloudcover)
344 1         2 $self->{TEMP_F} = undef; # current temp, celcius
345 1         2 $self->{TEMP_C} = undef; # converted to fahrenheit
346 1         2 $self->{DEW_F} = undef; # dew point, celcius
347 1         3 $self->{DEW_C} = undef; # dew point, fahrenheit
348 1         3 $self->{HOURLY_TEMP_F} = undef; # hourly current temp, celcius
349 1         2 $self->{HOURLY_TEMP_C} = undef; # hourly converted to fahrenheit
350 1         3 $self->{HOURLY_DEW_F} = undef; # hourly dew point, celcius
351 1         3 $self->{HOURLY_DEW_C} = undef; # hourly dew point, fahrenheit
352 1         2 $self->{HOURLY_PRECIP} = undef; # hourly precipitation
353 1         28 $self->{ALT} = undef; # altimeter setting
354 1         3 $self->{SLP} = undef; # sea level pressure
355 1         2 $self->{REMARKS} = undef; # remarks
356              
357 1         3 $self->{tokens} = [ ]; # the "token" list
358 1         3 $self->{type} = "METAR"; # the report type (METAR/SPECI)
359             # default=METAR
360 1         3 $self->{site} = undef; # the site code (4 chars)
361 1         2 $self->{date_time} = undef; # date/time
362 1         3 $self->{modifier} = "AUTO"; # the AUTO/COR modifier (if
363             # any) default=AUTO
364 1         2 $self->{wind} = undef; # the wind information
365 1         3 $self->{windtype} = undef; # the wind speed type (knots/meterpersecond/kilometersperhour)
366 1         2 $self->{windvar} = undef; # the wind variation
367 1         18 $self->{visibility} = undef; # visibility information
368 1         4 $self->{runway} = undef; # runway visibility
369 1         3 $self->{weather} = [ ]; # current weather conditions
370 1         3 $self->{sky} = [ ]; # sky conditions (cloud cover)
371 1         2 $self->{temp_dew} = undef; # temp and dew pt.
372 1         2 $self->{alt} = undef; # altimeter setting
373 1         22 $self->{pressure} = undef; # pressure (HPa)
374 1         4 $self->{slp} = undef; # sea level pressure
375 1         2 $self->{remarks} = [ ]; # remarks
376              
377 1         2 $self->{debug} = undef; # enable debug trace
378              
379 1         3 bless $self, $class;
380 1         4 return $self;
381             }
382              
383             ##
384             ## Autoload for access methods to stuff in %fields hash. We should
385             ## probably disallow access to the lower-case items as stated above,
386             ## but I don't feel like being a Nazi about it. Besides, I haven't
387             ## checked to see what that might break.
388             ##
389              
390             sub AUTOLOAD
391             {
392 4     4   1123 my $self = shift;
393              
394 4 50       12 if (not ref $self)
395             {
396 0         0 cluck "bad AUTOLOAD for obj [$self]";
397             }
398              
399 4 50       63 if ($AUTOLOAD =~ /.*::(.*)/)
400             {
401 4         9 my $key = $1;
402              
403              
404             ## Backward compatible temps...
405              
406 4         20 my %compat = (
407             F_TEMP => 'TEMP_F',
408             C_TEMP => 'TEMP_C',
409             F_DEW => 'DEW_F',
410             C_DEW => 'DEW_C',
411             );
412              
413 4 50       11 if ($compat{$key})
414             {
415 0         0 $key = $compat{$key};
416             }
417              
418             ## Check for the items...
419              
420 4 50       19 if (exists $self->{$key})
421             {
422 4         19 return $self->{$key};
423             }
424             else
425             {
426 0         0 return undef;
427             }
428             }
429             else
430             {
431 0         0 warn "strange AUTOLOAD problem!";
432 0         0 return undef;
433             }
434             }
435              
436             ##
437             ## Get current version number.
438             ##
439              
440             sub version
441             {
442 0     0 1 0 my $self = shift;
443 0 0       0 print "version() called.\n" if $self->{debug};
444 0         0 return $self->{VERSION};
445             }
446              
447             ##
448             ## Take a METAR, tokenize, and process it.
449             ##
450              
451             sub metar
452             {
453 1     1 1 70 my $self = shift;
454              
455 1 50       5 if (@_)
456             {
457 1         9 $self->{METAR} = shift;
458 1         5 $self->{METAR} =~ s/\n//g; ## nuke any newlines
459 1         4 _tokenize($self);
460 1         5 _process($self);
461             }
462 1         5 return $self->{METAR};
463             }
464              
465             ##
466             ## Break {METAR} into parts. Stuff into @tokens.
467             ##
468              
469             sub _tokenize
470             {
471 1     1   2 my $self = shift;
472 1         1 my $tok;
473             my @toks;
474              
475             # Split tokens on whitespace.
476 1         11 @toks = split(/\s+/, $self->{METAR});
477 1         4 $self->{tokens} = \@toks;
478             }
479              
480             ## Process @tokens to populate METAR values.
481             ##
482             ## This is a long and involved subroutine. It basically copies the
483             ## @tokens array and treats it as a stack, popping off items,
484             ## examining them, and see what they look like. Based on their
485             ## "apppearance" it takes care populating the proper fields
486             ## internally.
487              
488             sub _process
489             {
490 1     1   3 my $self = shift;
491              
492 1         1 my @toks = @{$self->{tokens}}; # copy tokens array...
  1         7  
493              
494 1         2 my $tok;
495              
496             ## This is a semi-brute-force way of doing things, but the amount
497             ## of data is relatively small, so it shouldn't be a big deal.
498             ##
499             ## Ideally, I'd have it skip checks for items which have been
500             ## found, but that would make this more "linear" and I'd remove
501             ## the pretty while loop.
502             #
503             # KH: modified to maintain state to not get lost in remarks and stuff
504             # and be a lot better at parsing
505            
506             # states
507              
508 1         2 my $expect_type = 0;
509 1         1 my $expect_site = 1;
510 1         36 my $expect_datetime = 2;
511 1         2 my $expect_modifier = 3;
512 1         2 my $expect_wind = 4;
513 1         1 my $expect_visibility = 5;
514 1         3 my $expect_runwayvisual = 6;
515 1         1 my $expect_presentweather = 7;
516 1         2 my $expect_clouds = 8;
517 1         2 my $expect_temperature = 9;
518 1         1 my $expect_pressure = 10;
519 1         2 my $expect_recentweather = 11;
520 1         2 my $expect_remarks = 12;
521 1         2 my $expect_usremarks = 13;
522              
523 1         2 my $parsestate = $expect_type;
524              
525             # windtypes
526            
527 1         2 my $wt_knots = 1;
528 1         1 my $wt_mps = 2;
529 1         2 my $wt_kph = 3;
530              
531             ## Assume standard report by default
532              
533 1         3 $self->{type} = "METAR";
534 1         2 $self->{TYPE} = "Routine Weather Report";
535              
536 1         13 while (defined($tok = shift(@toks))) ## as long as there are tokens
537             {
538 9 50       24 print "trying to match [$tok] state is $parsestate\n" if $self->{debug};
539              
540             ##
541             ## is it a report type?
542             ##
543              
544 9 50 66     3919 if (($parsestate == $expect_type) and ($tok =~ /(METAR|SPECI)/i))
    100 66        
    100 66        
    50 66        
    100 66        
    50 100        
    50 66        
    50 66        
    50 66        
    50 66        
    100 66        
    50 66        
    50 66        
    50 66        
    50 66        
    50 66        
    50 66        
    100 100        
    50 66        
    50 66        
    100 66        
    100 66        
    50 66        
    50 66        
    50 66        
    100 66        
    50 66        
    50 66        
    50 66        
    50 66        
    50 100        
    50 66        
    50 66        
    50 66        
      66        
      66        
      66        
      66        
      100        
      66        
      66        
      66        
      66        
      33        
      66        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
545             {
546 0         0 $self->{type} = $tok;
547              
548 0 0       0 if ($self->{type} eq "METAR")
    0          
549             {
550 0         0 $self->{TYPE} = "Routine Weather Report";
551             }
552             elsif ($self->{type} eq "SPECI")
553             {
554 0         0 $self->{TYPE} = "Special Weather Report";
555             }
556 0 0       0 print "[$tok] is a report type.\n" if $self->{debug};
557 0         0 $parsestate = $expect_site;
558 0         0 next;
559             }
560              
561             ##
562             ## is is a site ID?
563             ##
564              
565             elsif (($parsestate <= $expect_site) and ($tok =~ /([A-Z]{4}|K[A-Z0-9]{3})/))
566             {
567 1         31 $self->{site} = $tok;
568 1 50       4 print "[$tok] is a site ID.\n" if $self->{debug};
569 1         2 $parsestate = $expect_datetime;
570 1         5 next;
571             }
572              
573             ##
574             ## is it a date/time?
575             ##
576              
577             elsif (($parsestate == $expect_datetime) and ($tok =~ /\d{6,6}Z/i))
578             {
579 1         3 $self->{date_time} = $tok;
580 1 50       3 print "[$tok] is a date/time.\n" if $self->{debug};
581 1         9 $parsestate = $expect_modifier;
582 1         3 next;
583              
584              
585             }
586              
587             ##
588             ## is it a report modifier?
589             ##
590              
591             elsif (($parsestate == $expect_modifier) and ($tok =~ /AUTO|COR|CC[A-Z]/i))
592             {
593 0         0 $self->{modifier} = $tok;
594 0 0       0 print "[$tok] is a report modifier.\n" if $self->{debug};
595 0         0 $parsestate = $expect_wind;
596 0         0 next;
597             }
598              
599             ##
600             ## is it wind information in knots?
601             #
602             # eew: KT seems to be optional
603             # but making it optional fails on other stuff
604             # sortafix: wind needs to be \d\d\d\d\d or VRB\d\d
605             # optional \d\d\d\d\dG\d\d\d (gust direction)
606              
607             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_visibility) and ($tok =~ /(\d{3}|VRB)\d{2}(G\d{1,3})?(KT)?$/i))
608             {
609 1         4 $self->{wind} = $tok;
610 1         1201 $self->{windtype} = $wt_knots;
611 1 50       8 print "[$tok] is wind information in knots.\n" if $self->{debug};
612 1         2 $parsestate = $expect_wind; # stay in wind, it can have variation
613 1         6 next;
614             }
615              
616             ##
617             ## is it wind information in meters per second?
618             ##
619             ## can be variable too
620              
621             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_visibility) and ($tok =~ /^(\d{3}|VRB)\d{2}(G\d{2,3})?MPS$/))
622             {
623 0         0 $self->{wind} = $tok;
624 0 0       0 print "[$tok] is wind information.\n" if $self->{debug};
625 0         0 $self->{windtype} = $wt_mps;
626 0         0 $parsestate = $expect_wind; # stay in wind, it can have variation
627 0         0 next;
628             }
629              
630             ##
631             ## is it wind variation information?
632             ##
633              
634             elsif (($parsestate >= $expect_wind) and ($parsestate < $expect_visibility) and ($tok =~ /^\d{3}V\d{3}$/))
635             {
636 0         0 $self->{windvar} = $tok;
637 0 0       0 print "[$tok] is wind variation information.\n" if $self->{debug};
638 0         0 $parsestate = $expect_visibility;
639 0         0 next;
640             }
641              
642             ##
643             ## wind information missing at the moment?
644             ##
645              
646             elsif (($parsestate >= $expect_wind) and ($parsestate < $expect_visibility) and ($tok =~ /^\/\/\/\/\/(KT|MPS)$/)){
647 0 0       0 print "[$tok] is missing wind information.\n" if $self->{debug};
648 0         0 $parsestate = $expect_visibility;
649 0         0 next;
650             }
651              
652             ##
653             ## is it visibility information in meters?
654             ##
655            
656             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /^\d{4}$/))
657             {
658 0         0 $self->{visibility} = $tok;
659 0 0       0 print "[$tok] is numerical visibility information.\n" if $self->{debug};
660 0         0 $parsestate = $expect_visibility;
661 0         0 next;
662             }
663              
664             ## auto visibility information in meters?
665              
666             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /^\d{4}NDV$/))
667             {
668 0         0 $self->{visibility} = $tok;
669 0 0       0 print "[$tok] is automatic numerical visibility information.\n" if $self->{debug};
670 0         0 $parsestate = $expect_visibility;
671 0         0 next;
672             }
673              
674             ##
675             ## is it visibility information in statute miles?
676             ##
677              
678             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /.*?SM$/i))
679             {
680 1         4 $self->{visibility} = $tok;
681 1 50       5 print "[$tok] is statute miles visibility information.\n" if $self->{debug};
682 1         2 $parsestate = $expect_visibility;
683 1         4 next;
684             }
685              
686             ##
687             ## is it visibility information with a leading digit?
688             ##
689             ## sample:
690             ## KERV 132345Z AUTO 07008KT 1 1/4SM HZ 34/11 A3000 RMK AO2
691             ## ^^^^^^^
692             ##
693              
694             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /^\d$/))
695             {
696 0         0 $tok .= " " . shift(@toks);
697 0         0 $self->{visibility} = $tok;
698 0 0       0 print "[$tok] is multi-part visibility information.\n" if $self->{debug};
699 0         0 $parsestate = $expect_visibility;
700 0         0 next;
701             }
702              
703             ## visibility modifier
704              
705             elsif (($parsestate == $expect_visibility) and ($tok =~ /^\d{4}(N|S|E|W|NE|NW|SE|SW)$/))
706             {
707 0 0       0 print "[$tok] is a visibility modifier.\n" if $self->{debug};
708 0         0 next;
709             }
710              
711             ##
712             ## is it runway visibility info?
713             ##
714             # KH: I've seen runway visibility with 'U' units
715             # EHSB 121425Z 22010KT 1200 R27/1600U -DZ BKN003 OVC007 07/07 Q1016 AMB FCST CANCEL
716             # U= going up, D= going down, N= no change
717             # tendency of visual range, http://stoivane.kapsi.fi/metar/
718              
719             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_presentweather) and ($tok =~ /R\d+(L|R|C)?\/P?\d+(VP?\d+)?(FT|D|U|N|\/)?$/i))
720             {
721 0         0 push (@{$self->{RUNWAY}},$tok);
  0         0  
722 0 0       0 print "[$tok] is runway visual information.\n" if $self->{debug};
723 0         0 $parsestate = $expect_runwayvisual;
724             # there can be multiple runways, so stay at this state
725 0         0 next;
726             }
727              
728             ##
729             ## is it current weather info?
730             ##
731              
732             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_clouds) and ($tok =~ /^(-|\+)?(VC)?($_weather_types_pat)+/i))
733             {
734 0         0 my $engl = "";
735 0         0 my $qual = $1;
736 0         0 my $addlqual = $2;
737              
738             ## qualifier
739              
740 0 0       0 if (defined $qual)
741             {
742 0 0       0 if ( $qual eq "-" ) {
    0          
743 0         0 $engl = "light";
744             } elsif ( $qual eq "+" ) {
745 0         0 $engl = "heavy";
746             } else {
747 0         0 $engl = ""; ## moderate
748             }
749             }
750             else
751             {
752 0         0 $engl = ""; ## moderate
753             }
754              
755 0         0 while ( $tok =~ /($_weather_types_pat)/gi )
756             {
757 0         0 $engl .= " " . $_weather_types{$1}; ## figure out weather
758             }
759              
760             ## addl qualifier
761              
762 0 0       0 if (defined $addlqual)
763             {
764 0 0       0 if ( $addlqual eq "VC" )
765             {
766 0         0 $engl .= " in vicinity";
767             }
768             }
769              
770 0         0 $engl =~ s/^\s//gio;
771 0         0 $engl =~ s/\s\s/ /gio;
772              
773 0         0 push(@{$self->{WEATHER}},$engl);
  0         0  
774 0         0 push(@{$self->{weather}},$tok);
  0         0  
775 0 0       0 print "[$tok] is current weather.\n" if $self->{debug};
776 0         0 $parsestate = $expect_presentweather;
777             # there can be multiple current weather types, so stay at this state
778 0         0 next;
779             }
780              
781             ##
782             ## special case: CAVOK
783             ##
784            
785             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok eq 'CAVOK' ))
786             {
787 0         0 push(@{$self->{sky}},$tok);
  0         0  
788 0         0 push(@{$self->{SKY}}, "Sky Clear");
  0         0  
789 0         0 push(@{$self->{weather}},$tok);
  0         0  
790 0         0 push(@{$self->{WEATHER}},"No significant weather");
  0         0  
791 0         0 $self->{visibility} = '9999';
792 0         0 $parsestate = $expect_temperature;
793 0         0 next;
794             }
795              
796             ##
797             ## is it sky conditions (clear)?
798             ##
799              
800             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /SKC|CLR/ ))
801             {
802 0         0 push(@{$self->{sky}},$tok);
  0         0  
803 0         0 push(@{$self->{SKY}}, "Sky Clear");
  0         0  
804 0 0       0 print "[$tok] is a sky condition.\n" if $self->{debug};
805 0         0 $parsestate = $expect_clouds;
806 0         0 next;
807             }
808              
809             ##
810             ## is it sky conditions (clouds)?
811             ##
812             ## sky conditions can end with ///
813              
814             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^(FEW|SCT|BKN|OVC)(\d\d\d)?(CB|TCU)?\/*$/i))
815             {
816 1         644 push(@{$self->{sky}},$tok);
  1         7  
817 1         3 my $engl = "";
818              
819 1         7 $engl = $_sky_types{$1};
820              
821 1 50       4 if (defined $3)
822             {
823 0 0       0 if ($3 eq "TCU")
    0          
824             {
825 0         0 $engl .= " Towering Cumulus";
826             }
827             elsif ($3 eq "CB")
828             {
829 0         0 $engl .= " Cumulonimbus";
830             }
831             }
832              
833 1 50       5 if ($2 ne "")
834             {
835 1         5 my $agl = int($2)*100;
836 1         5 $engl .= " at $agl" . "ft";
837             }
838              
839 1         1 push(@{$self->{SKY}}, $engl);
  1         4  
840 1 50       3 print "[$tok] is a sky condition.\n" if $self->{debug};
841 1         2 $parsestate = $expect_clouds;
842             # clouds DO repeat. a lot ;)
843 1         10 next;
844             }
845              
846             ##
847             ## auto detected cloud conditions
848             ##
849              
850             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^(NSC|NCD)$/ )){
851 0         0 my $engl = "";
852              
853 0         0 $engl = $_sky_types{$tok};
854 0         0 push(@{$self->{SKY}}, $engl);
  0         0  
855 0 0       0 print "[$tok] is an automatic sky condition.\n" if $self->{debug};
856 0         0 $parsestate = $expect_temperature;
857 0         0 next;
858             }
859              
860             ##
861             ## Vertical visibility
862             ##
863              
864             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^VV\d+$/ )){
865 0 0       0 print "[$tok] is vertical visibility.\n" if $self->{debug};
866 0         0 $parsestate = $expect_temperature;
867 0         0 next;
868             }
869              
870             ##
871             ## is it temperature and dew point info?
872             ##
873              
874             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_pressure) and ($tok =~ /^(M?\d\d)\/(M?\d{0,2})/i))
875             {
876 1 50       4 next if $self->{temp_dew};
877 1         2 $self->{temp_dew} = $tok;
878              
879 1         3 $self->{TEMP_C} = $1;
880 1         7 $self->{DEW_C} = $2;
881 1         3 $self->{TEMP_C} =~ s/^M/-/;
882 1         5 $self->{DEW_C} =~ s/^M/-/;
883              
884 1 50       3 print "[$tok] is temperature/dew point information.\n" if $self->{debug};
885 1         1 $parsestate = $expect_pressure;
886 1         3 next;
887             }
888              
889             ##
890             ## is it an altimeter setting?
891             ##
892              
893             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^A(\d\d)(\d\d)$/i))
894             {
895 1         2 $self->{alt} = $tok;
896 1         5 $self->{ALT} = "$1.$2";
897              
898             # inches Hg pressure. How imperial can you get
899             # conversion using 'units'
900              
901 1         5 $self->{pressure} = 33.863886 * $self->{ALT};
902              
903 1 50       4 print "[$tok] is an altimeter setting.\n" if $self->{debug};
904 1         2 $parsestate = $expect_recentweather;
905 1         3 next;
906             }
907              
908             ##
909             ## is it a pressure?
910             ##
911              
912             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^Q(\d\d\d\d)$/i))
913             {
914 0         0 $self->{pressure} = $1;
915              
916 0         0 $self->{ALT} = 0.029529983*$self->{pressure};
917 0 0       0 print "[$tok] is an air pressure.\n" if $self->{debug};
918 0         0 $parsestate = $expect_recentweather;
919 0         0 next;
920             }
921              
922             ##
923             ## recent weather?
924             ##
925              
926             elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^RE($_weather_types_pat)$/)){
927 0 0       0 print "[$tok] is recent significant weather.\n" if $self->{debug};
928 0         0 $parsestate = $expect_remarks;
929 0         0 next;
930             }
931              
932             ##
933             ## euro type trend?
934             ##
935              
936             elsif (($parsestate >= $expect_modifier) and ($tok =~ /^$_trend_types_pat/)){
937 0 0       0 print "[$tok] is a trend.\n" if $self->{debug};
938 0         0 $parsestate = $expect_remarks;
939 0         0 next;
940             }
941              
942             ##
943             ## us type remarks? .. can happen quite early in the process already
944             ##
945              
946             elsif (($parsestate >= $expect_modifier) and ($tok =~ /^RMK$/i))
947             {
948 1         2 push(@{$self->{remarks}},$tok);
  1         5  
949 1 50       66 print "[$tok] is a (US type) remark.\n" if $self->{debug};
950 1         4 $parsestate = $expect_usremarks;
951 1         8 next;
952             }
953              
954             ##
955             ## automatic station type?
956             ##
957              
958             elsif (($parsestate == $expect_usremarks) and ($tok =~ /^A(O\d)$/i))
959             {
960 0         0 $self->{autostationtype} = $tok;
961 0         0 $self->{AUTO_STATIONTYPE} = $1;
962 0 0       0 print "[$tok] is an automatic station type remark.\n" if $self->{debug};
963 0         0 next;
964             }
965              
966             ##
967             ## sea level pressure
968             ##
969              
970             elsif (($parsestate == $expect_usremarks) and ($tok =~ /^SLP(\d+)/i))
971             {
972 0         0 $self->{slp} = $tok;
973 0         0 $self->{SLP} = "$1 mb";
974 0 0       0 print "[$tok] is a sea level pressure.\n" if $self->{debug};
975 0         0 next;
976             }
977              
978             ##
979             ## sea level pressure not available
980             ##
981              
982             elsif (($parsestate == $expect_usremarks) and ($tok eq "SLPNO"))
983             {
984 0         0 $self->{slp} = "SLPNO";
985 0         0 $self->{SLP} = "not available";
986 0 0       0 print "[$tok] is a sea level pressure.\n" if $self->{debug};
987 0         0 next;
988             }
989              
990             ##
991             ## hourly precipitation
992             ##
993              
994             elsif (($parsestate == $expect_usremarks) and ($tok =~ /^P(\d\d\d\d)$/i))
995             {
996 0         0 $self->{hourlyprecip} = $tok;
997              
998 0 0       0 if ( $1 eq "0000" ) {
999 0         0 $self->{HOURLY_PRECIP} = "Trace";
1000             } else {
1001 0         0 $self->{HOURLY_PRECIP} = $1;
1002             }
1003             }
1004              
1005             ##
1006             ## weather begin/end times
1007             ##
1008              
1009             elsif (($parsestate == $expect_usremarks) and ($tok =~ /^($_weather_types_pat)([BE\d]+)$/i))
1010             {
1011 0         0 my $engl = "";
1012 0         0 my $times = $2;
1013              
1014 0         0 $self->{weatherlog} = $tok;
1015              
1016 0         0 $engl = $_weather_types{$1};
1017              
1018 0         0 while ( $times =~ /(B|E)(\d\d)/g )
1019             {
1020 0 0       0 if ( $1 eq "B" ) {
1021 0         0 $engl .= " began :$2";
1022             } else {
1023 0         0 $engl .= " ended :$2";
1024             }
1025             }
1026              
1027 0         0 push(@{$self->{WEATHER_LOG}}, $engl);
  0         0  
1028 0 0       0 print "[$tok] is a weather log.\n" if $self->{debug};
1029 0         0 next;
1030             }
1031              
1032             ##
1033             ## remarks for significant cloud types
1034             ##
1035              
1036             elsif (($parsestate >= $expect_recentweather) and ($tok eq "CB" || $tok eq "TCU"))
1037             {
1038 0         0 push(@{$self->{sigclouds}}, $tok);
  0         0  
1039              
1040 0 0       0 if ( $tok eq "CB" ) {
    0          
1041 0         0 push(@{$self->{SIGCLOUDS}}, "Cumulonimbus");
  0         0  
1042             } elsif ( $tok eq "TCU" ) {
1043 0         0 push(@{$self->{SIGCLOUDS}}, "Towering Cumulus");
  0         0  
1044             }
1045 0         0 $parsestate = $expect_usremarks;
1046             }
1047              
1048             ##
1049             ## hourly temp/dewpoint
1050             ##
1051              
1052             elsif (($parsestate == $expect_usremarks) and ($tok =~ /^T(\d)(\d\d)(\d)(\d)(\d\d)(\d)$/i))
1053             {
1054 0         0 $self->{hourlytempdew} = $tok;
1055 0 0       0 if ( $1 == 1 ) {
1056 0         0 $self->{HOURLY_TEMP_C} = "-";
1057             }
1058 0         0 $self->{HOURLY_TEMP_C} .= "$2.$3";
1059              
1060 0         0 $self->{HOURLY_DEW_C} = "";
1061 0 0       0 if ( $4 == 1 ) {
1062 0         0 $self->{HOURLY_DEW_C} = "-";
1063             }
1064 0         0 $self->{HOURLY_DEW_C} .= "$5.$6";
1065              
1066 0 0       0 print "[$tok] is a hourly temp and dewpoint.\n" if $self->{debug};
1067 0         0 next;
1068             }
1069              
1070             ##
1071             ## unknown, not in remarks yet
1072             ##
1073              
1074             elsif ($parsestate < $expect_remarks)
1075             {
1076 0         0 push(@{$self->{unknown}},$tok);
  0         0  
1077 0         0 push(@{$self->{UNKNOWN}},$tok);
  0         0  
1078 0 0       0 print "[$tok] is unexpected at this state.\n" if $self->{debug};
1079 0         0 next;
1080             }
1081              
1082             ##
1083             ## unknown. assume remarks
1084             ##
1085              
1086             else
1087             {
1088 1         3 push(@{$self->{remarks}},$tok);
  1         4  
1089 1         2 push(@{$self->{REMARKS}},$tok);
  1         4  
1090 1 50       4 print "[$tok] is unknown remark.\n" if $self->{debug};
1091 1         13 next;
1092             }
1093              
1094             }
1095              
1096             ##
1097             ## Now that the internal stuff is set, let's do the external
1098             ## stuff.
1099             ##
1100              
1101 1         5 $self->{SITE} = $self->{site};
1102 1         4 $self->{DATE} = substr($self->{date_time},0,2);
1103 1         5 $self->{TIME} = substr($self->{date_time},2,4) . " UTC";
1104 1         14 $self->{TIME} =~ s/(\d\d)(\d\d)/$1:$2/o;
1105 1         4 $self->{MOD} = $self->{modifier};
1106              
1107             ##
1108             ## Okay, wind finally gets interesting.
1109             ##
1110              
1111 1 50       6 if ( defined $self->{wind} )
1112             {
1113 1         2 my $wind = $self->{wind};
1114 1         3 my $dir_deg = substr($wind,0,3);
1115 1         3 my $dir_eng = "";
1116 1         1 my $dir_abb = "";
1117              
1118             # Check for wind direction
1119 1 50       5 if ($dir_deg =~ /VRB/i) {
1120 0         0 $dir_deg = "Variable";
1121             } else {
1122 1 50       18 if ($dir_deg < 15) {
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
1123 0         0 $dir_eng = "North";
1124 0         0 $dir_abb = "N";
1125             } elsif ($dir_deg < 30) {
1126 0         0 $dir_eng = "North/Northeast";
1127 0         0 $dir_abb = "NNE";
1128             } elsif ($dir_deg < 60) {
1129 0         0 $dir_eng = "Northeast";
1130 0         0 $dir_abb = "NE";
1131             } elsif ($dir_deg < 75) {
1132 0         0 $dir_eng = "East/Northeast";
1133 0         0 $dir_abb = "ENE";
1134             } elsif ($dir_deg < 105) {
1135 0         0 $dir_eng = "East";
1136 0         0 $dir_abb = "E";
1137             } elsif ($dir_deg < 120) {
1138 0         0 $dir_eng = "East/Southeast";
1139 0         0 $dir_abb = "ESE";
1140             } elsif ($dir_deg < 150) {
1141 0         0 $dir_eng = "Southeast";
1142 0         0 $dir_abb = "SE";
1143             } elsif ($dir_deg < 165) {
1144 0         0 $dir_eng = "South/Southeast";
1145 0         0 $dir_abb = "SSE";
1146             } elsif ($dir_deg < 195) {
1147 0         0 $dir_eng = "South";
1148 0         0 $dir_abb = "S";
1149             } elsif ($dir_deg < 210) {
1150 0         0 $dir_eng = "South/Southwest";
1151 0         0 $dir_abb = "SSW";
1152             } elsif ($dir_deg < 240) {
1153 1         2 $dir_eng = "Southwest";
1154 1         2 $dir_abb = "SW";
1155             } elsif ($dir_deg < 265) {
1156 0         0 $dir_eng = "West/Southwest";
1157 0         0 $dir_abb = "WSW";
1158             } elsif ($dir_deg < 285) {
1159 0         0 $dir_eng = "West";
1160 0         0 $dir_abb = "W";
1161             } elsif ($dir_deg < 300) {
1162 0         0 $dir_eng = "West/Northwest";
1163 0         0 $dir_abb = "WNW";
1164             } elsif ($dir_deg < 330) {
1165 0         0 $dir_eng = "Northwest";
1166 0         0 $dir_abb = "NW";
1167             } elsif ($dir_deg < 345) {
1168 0         0 $dir_eng = "North/Northwest";
1169 0         0 $dir_abb = "NNW";
1170             } else {
1171 0         0 $dir_eng = "North";
1172 0         0 $dir_abb = "N";
1173             }
1174             }
1175              
1176 1         1 my $kts_speed = undef;
1177 1         3 my $mph_speed = undef;
1178              
1179 1         2 my $kts_gust = "";
1180 1         2 my $mph_gust = "";
1181              
1182             # parse knots
1183              
1184 1 50       3 if ($self->{windtype} == $wt_knots){
    0          
1185 1         4 $wind =~ /...(\d\d\d?)/o;
1186 1         2 $kts_speed = $1;
1187 1         5 $mph_speed = $kts_speed * 1.1508;
1188              
1189              
1190 1 50       11 if ($wind =~ /.{5,6}G(\d\d\d?)/o) {
1191 1         2 $kts_gust = $1;
1192 1         2 $mph_gust = $kts_gust * 1.1508;
1193             }
1194             # else: parse meters/second
1195             } elsif ($self->{windtype} == $wt_mps){
1196 0         0 $wind=~ /...(\d\d\d?)/o;
1197 0         0 my $mps_speed = $1;
1198 0         0 $kts_speed = $mps_speed * 1.9438445; # units
1199 0         0 $mph_speed = $mps_speed * 2.2369363;
1200 0 0       0 if ($wind =~ /\d{5,6}G(\d\d\d?)/o) {
1201 0         0 my $mps_gust = $1;
1202 0         0 $kts_gust = $mps_gust * 1.9438445;
1203 0         0 $mph_gust = $mps_gust * 2.2369363;
1204             }
1205             } else {
1206 0         0 warn "Geo::METAR Parser error: unknown windtype\n";
1207             }
1208              
1209 1         3 $self->{WIND_KTS} = $kts_speed;
1210 1         3 $self->{WIND_MPH} = $mph_speed;
1211              
1212 1         2 $self->{WIND_GUST_KTS} = $kts_gust;
1213 1         2 $self->{WIND_GUST_MPH} = $mph_gust;
1214              
1215 1         36 $self->{WIND_DIR_DEG} = $dir_deg;
1216 1         3 $self->{WIND_DIR_ENG} = $dir_eng;
1217 1         2 $self->{WIND_DIR_ABB} = $dir_abb;
1218              
1219             }
1220              
1221             ##
1222             ## wind variation
1223             ##
1224              
1225 1 50       4 if (defined $self->{windvar})
1226             {
1227 0 0       0 if ($self->{windvar} =~ /^(\d\d\d)V(\d\d\d)$/){
1228 0         0 $self->{WIND_VAR} = "Varying between $1 and $2";
1229 0         0 $self->{WIND_VAR_1} = $1;
1230 0         0 $self->{WIND_VAR_2} = $2;
1231             }
1232             }
1233              
1234             ##
1235             ## Visibility.
1236             ##
1237              
1238             {
1239 1         2 my $vis = $self->{visibility};
  1         2  
1240             # test for statute miles
1241 1 50 0     4 if ($vis =~ /SM$/){
    0          
1242 1         5 $vis =~ s/SM$//oi; # nuke the "SM"
1243 1 50       3 if ($vis =~ /M(\d\/\d)/o) {
1244 0         0 $self->{VISIBILITY} = "Less than $1 statute miles";
1245             } else {
1246 1         5 $self->{VISIBILITY} = $vis . " Statute Miles";
1247             } # end if
1248             # auto metars can have non-directional visibility reports
1249             } elsif (($self->{MOD} eq 'AUTO') and ($vis =~ /(\d+)NDV$/)){
1250 0         0 $self->{VISIBILITY} = "$1 meters non-directional visibility";
1251             } else {
1252 0         0 $self->{VISIBILITY} = $vis . " meters";
1253             }
1254             }
1255              
1256             ##
1257             ## Calculate F temps for all C temps
1258             ##
1259              
1260 1         11 foreach my $key ( keys(%$self) )
1261             {
1262 52 100 100     175 if ( uc($key) eq $key && $key =~ /^(.*)_C$/ )
1263             {
1264 4         11 my $fkey = $1 . "_F";
1265              
1266 4 100 66     25 next unless defined $self->{$key} && $self->{$key};
1267              
1268 2         37 $self->{$fkey} = sprintf("%.1f", (($self->{$key} * (9/5)) + 32));
1269             }
1270             }
1271              
1272             # join the runway group
1273            
1274 1         5 $self->{runway} = join(', ' , @{$self->{RUNWAY}});
  1         7  
1275            
1276             }
1277              
1278             ##
1279             ## Print the tokens--usually when debugging.
1280             ##
1281              
1282             sub print_tokens
1283             {
1284 0     0 0   my $self = shift;
1285 0           my $tok;
1286 0           foreach $tok (@{$self->{tokens}}) {
  0            
1287 0           print "> $tok\n";
1288             }
1289             }
1290              
1291             ##
1292             ## Turn debugging on/off.
1293             ##
1294              
1295             sub debug
1296             {
1297 0     0 1   my $self = shift;
1298 0           my $flag = shift;
1299 0 0         return $self->{debug} unless defined $flag;
1300              
1301 0 0 0       if (($flag eq "Y") or ($flag eq "y") or ($flag == 1)) {
    0 0        
      0        
      0        
1302 0           $self->{debug} = 1;
1303             } elsif (($flag eq "N") or ($flag eq "n") or ($flag == 0)) {
1304 0           $self->{debug} = 0;
1305             }
1306              
1307 0           return $self->{debug};
1308             }
1309              
1310             ##
1311             ## Dump internal data structure. Useful for debugging and such.
1312             ##
1313              
1314             sub dump
1315             {
1316 0     0 1   my $self = shift;
1317              
1318 0           print "Modified METAR dump follows.\n\n";
1319              
1320 0           print "type: $self->{type}\n";
1321 0           print "site: $self->{site}\n";
1322 0           print "date_time: $self->{date_time}\n";
1323 0           print "modifier: $self->{modifier}\n";
1324 0           print "wind: $self->{wind}\n";
1325 0           print "visibility: $self->{visibility}\n";
1326 0           print "runway: $self->{runway}\n";
1327 0           print "weather: " . join(', ', @{$self->{weather}}) . "\n";
  0            
1328 0           print "sky: " . join(', ', @{$self->{sky}}) . "\n";
  0            
1329 0           print "temp_dew: $self->{temp_dew}\n";
1330 0           print "alt: $self->{ALT}\n";
1331 0           print "slp: $self->{slp}\n";
1332 0           print "remarks: " . join (', ', @{$self->{remarks}}) . "\n";
  0            
1333 0           print "\n";
1334              
1335 0           foreach my $var ( sort(keys(%$self)) )
1336             {
1337 0 0         next if ( uc($var) ne $var );
1338              
1339 0 0         if ( ref($self->{$var}) eq "ARRAY" )
1340             {
1341 0           print "$var: ", join(", ", @{$self->{$var}}), "\n";
  0            
1342             }
1343             else
1344             {
1345 0           print "$var: ", $self->{$var}, "\n";
1346             }
1347             }
1348             }
1349              
1350             1;
1351              
1352             __END__