~ubuntu-branches/ubuntu/trusty/libgeo-metar-perl/trusty

« back to all changes in this revision

Viewing changes to METAR.pm

  • Committer: Bazaar Package Importer
  • Author(s): Martín Ferrari
  • Date: 2008-06-13 02:42:42 UTC
  • mfrom: (0.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20080613024242-2be2lbxarhx8sglx
Tags: 1.15-1
* New upstream release, new upstream maintainer.
* debian/docs: removed HACKING.
* debian/copyright: updated CP info, plus new format.
* debian/control: added myself to Uploaders, fixed case in short desc. Fix
  incorrect Build-Depends{,-Indep} distribution.
* debian/patches: finally merged all the patches from #304962 and #262397
  with the new upstream release! Also, sent the patches upstream as
  CPAN#36708. (Closes: #304962).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# $Id: METAR.pm,v 1.8 2000/11/25 00:07:38 jzawodn Exp $
 
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
#
2
30
 
3
31
# This module is used for decoding NWS METAR code.
4
32
 
18
46
# 9/205 51007
19
47
#
20
48
# LA
 
49
#
21
50
# KLAX 251450Z 07004KT 7SM SCT100 BKN200 14/11 A3005 RMK AO2 SLP173
22
51
# T01390111 56005
 
52
#
 
53
# Soesterberg
 
54
#
 
55
# EHSB 181325Z 24009KT 8000 -RA BR FEW011 SCT022 OVC030 07/06 Q1011 WHT WHT TEMPO GRN
23
56
 
24
57
# For METAR info, please see
25
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
 
26
67
 
27
68
# The METAR specification is dictated in the Federal Meteorological Handbook
28
69
# which is available on-line at:
29
70
# http://tgsv5.nws.noaa.gov/oso/oso1/oso12/fmh1.htm
30
71
 
31
72
# General Structure is:
32
 
# SITE, DATE/TIME, WIND, VISIBILITY, CLOUDS, TEMPERATURE, PRESSURE, REMARKS
 
73
# TYPE, SITE, DATE/TIME, WIND, VISIBILITY, CLOUDS, TEMPERATURE, PRESSURE, REMARKS
33
74
 
34
75
# Specifically:
35
76
 
36
 
# SITE
 
77
# TYPE (optional)
 
78
# METAR or SPECI
 
79
# METAR: regular report
 
80
# SPECI: special report
 
81
 
 
82
# SITE (required, only once)
37
83
#
38
84
# 4-Char site identifier (KLAX for LA, KHST for Houston)
39
85
 
40
 
# DATE/TIME
 
86
# DATE/TIME (required, only once)
41
87
#
42
88
# 6-digit time followed by "Z", indicating UTC
43
89
 
44
 
# WIND
 
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)
45
96
#
46
97
# Wind direction (\d\d\d) and speed (\d?\d\d) and optionaling gusting
47
98
# information denoted by "G" and speed (\d?\d\d) followed by "KT", for knots.
48
99
#
49
100
# Wind direction MAY be "VRB" (variable) instead of a compass direction.
50
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
#
51
106
# Calm wind is recorded as 00000KT.
52
107
 
53
 
# VISIBILITY
 
108
# VISIBILITY (group)
54
109
#
55
 
# Visibility (\d+) followed by "SM" for statute miles
 
110
# Visibility (\d+) followed by "SM" for statute miles or no 'SM' for meters
 
111
# (european)
56
112
#
57
113
# May be 1/(\d)SM for a fraction.
58
114
#
59
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
60
124
 
61
 
# RUNWAY Visual Range Group (I've never seen this, but it's in the spec)
 
125
# RUNWAY Visual Range (Group)
62
126
#
63
127
# R(\d\d\d)(L|C|R)?/((M|P)?\d\d\d\d){1,2}FT
64
128
#
70
134
#
71
135
#  "M" beginning a value means less than the reportable value of \d\d\d\d.
72
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
73
147
 
74
148
# WEATHER (Present Weather Group)
75
149
#
85
159
# The report may have a trailing CB (cumulonimbus) or TCU (towering
86
160
# cumulus) appended. ([A-Z]{2,3})?(\d\d\d)(CB|TCU)?
87
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
88
178
# TEMPERATURE and DEW POINT
89
179
#
90
180
# (M?\d\d)/(M?\d\d) where $1 is the current temperature in degrees celcius,
95
185
 
96
186
# PRESSURE
97
187
#
98
 
# The pressure, or altimeter setting, at the reporting site recorded in inches
99
 
# of mercury (Hg) minus the decimal point. It should always look like
100
 
# (A\d\d\d\d).
 
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).
101
191
#
102
 
# Note: The WMO standard is to report the altimeter in whole hectopascals. In
103
 
# this case, the altimeter setting group will be begin with a Q instead of an
104
 
# A.
 
192
# KNMI: Q\d\d\d\d pressure in hPa calculated for sea level
105
193
 
106
194
# REMARKS
107
195
#
109
197
# informative of special conditions.
110
198
#
111
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
112
220
 
113
221
### Package Definition
114
222
 
121
229
use vars qw($AUTOLOAD $VERSION);
122
230
use Carp 'cluck';
123
231
 
124
 
$VERSION = '1.14';
 
232
$VERSION = '1.15';
125
233
 
126
234
##
127
235
## Lookup tables
128
236
##
129
237
 
130
238
my %_weather_types = (
131
 
        MI => 'shallow',
132
 
        PI => 'partial',
133
 
        BC => 'patches',
134
 
        DR => 'drizzle',
135
 
        BL => 'blowing',
136
 
        SH => 'shower(s)',
137
 
        TS => 'thunderstorm',
138
 
        FZ => 'freezing',
139
 
 
140
 
        DZ => 'drizzle',
141
 
        RA => 'rain',
142
 
        SN => 'snow',
143
 
        SG => 'snow grains',
144
 
        IC => 'ice crystals',
145
 
        PE => 'ice pellets',
146
 
        GR => 'hail',
147
 
        GS => 'small hail/snow pellets',
148
 
        UP => 'unknown precip',
149
 
 
150
 
        BR => 'mist',
151
 
        FG => 'fog',
152
 
        FU => 'smoke',
153
 
        VA => 'volcanic ash',
154
 
        DU => 'dust',
155
 
        SA => 'sand',
156
 
        HZ => 'haze',
157
 
        PY => 'spray',
158
 
 
159
 
        PO => 'dust/sand whirls',
160
 
        SQ => 'squalls',
161
 
        FC => 'funnel cloud(tornado/waterspout)',
162
 
        SS => 'sand storm',
163
 
        DS => 'dust storm'
164
 
    );
 
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
);
165
274
 
166
275
my $_weather_types_pat = join("|", keys(%_weather_types));
167
276
 
168
277
my %_sky_types = (
169
 
        SKC => "Sky Clear",
170
 
        CLR => "Sky Clear",
171
 
        SCT => "Scattered",
172
 
        BKN => "Broken",
173
 
        FEW => "Few",
174
 
        OVC => "Solid Overcast",
175
 
);
 
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));
176
305
 
177
306
##
178
307
## Constructor.
198
327
    $self->{TIME}          = undef;             # time it was issued
199
328
    $self->{MOD}           = undef;             # modifier (AUTO/COR)
200
329
    $self->{WIND_DIR_DEG}  = undef;             # wind dir in degrees
201
 
    $self->{WIND_DIR_ENG}  = undef;             # wind dir in english (NW/SE)
 
330
    $self->{WIND_DIR_ENG}  = undef;             # wind dir in english (Northwest/Southeast)
 
331
    $self->{WIND_DIR_ABB}  = undef;             # wind dir in abbreviated english (NW/SE)
202
332
    $self->{WIND_KTS}      = undef;             # wind speed (knots)
203
333
    $self->{WIND_GUST_KTS} = undef;             # wind gusts (knots)
204
334
    $self->{WIND_MPH}      = undef;             # wind speed (MPH)
205
335
    $self->{WIND_GUST_MPH} = undef;             # wind gusts (MPH)
206
 
    $self->{WIND_VAR_DEG}  = undef;             # wind variation in degrees
207
 
    $self->{WIND_VAR_ENG}  = undef;             # wind variation in english
 
336
    $self->{WIND_VAR}      = undef;             # wind variation (text)
 
337
    $self->{WIND_VAR_1}    = undef;             # wind variation (direction 1)
 
338
    $self->{WIND_VAR_2}    = undef;             # wind variation (direction 2)
208
339
    $self->{VISIBILITY}    = undef;             # visibility info
209
 
    $self->{RUNWAY}        = undef;             # runyway vis.
 
340
    $self->{RUNWAY}        = [ ];               # runway vis.
210
341
    $self->{WEATHER}       = [ ];               # current weather
211
342
    $self->{WEATHER_LOG}   = [ ];               # weather log
212
 
    $self->{SKY}           = [ ];               # curent sky
 
343
    $self->{SKY}           = [ ];               # current sky (cloudcover)
213
344
    $self->{TEMP_F}        = undef;             # current temp, celcius
214
 
    $self->{TEMP_C}        = undef;             # converted to farenheit
 
345
    $self->{TEMP_C}        = undef;             # converted to fahrenheit
215
346
    $self->{DEW_F}         = undef;             # dew point, celcius
216
 
    $self->{DEW_C}         = undef;             # dew point, farenheit
 
347
    $self->{DEW_C}         = undef;             # dew point, fahrenheit
217
348
    $self->{HOURLY_TEMP_F} = undef;             # hourly current temp, celcius
218
 
    $self->{HOURLY_TEMP_C} = undef;             # hourly converted to farenheit
 
349
    $self->{HOURLY_TEMP_C} = undef;             # hourly converted to fahrenheit
219
350
    $self->{HOURLY_DEW_F}  = undef;             # hourly dew point, celcius
220
 
    $self->{HOURLY_DEW_C}  = undef;             # hourly dew point, farenheit
 
351
    $self->{HOURLY_DEW_C}  = undef;             # hourly dew point, fahrenheit
221
352
    $self->{HOURLY_PRECIP} = undef;             # hourly precipitation
222
 
    $self->{ALT}           = undef;             # altimeter setting (Hg)
223
 
    $self->{ALT_HP}        = undef;             # altimeter setting (hPa)
 
353
    $self->{ALT}           = undef;             # altimeter setting
224
354
    $self->{SLP}           = undef;             # sea level pressure
225
 
    $self->{REMARKS}       = undef;             # remarks and such
 
355
    $self->{REMARKS}       = undef;             # remarks
226
356
 
227
357
    $self->{tokens}        = [ ];               # the "token" list
228
358
    $self->{type}          = "METAR";           # the report type (METAR/SPECI)
229
359
                                                # default=METAR
230
360
    $self->{site}          = undef;             # the site code (4 chars)
231
361
    $self->{date_time}     = undef;             # date/time
232
 
    $self->{modifier}      = undef;             # the AUTO/COR modifier
 
362
    $self->{modifier}      = "AUTO";            # the AUTO/COR modifier (if
 
363
                                                # any) default=AUTO
233
364
    $self->{wind}          = undef;             # the wind information
234
 
    $self->{vrbwind}       = undef;             # variable wind information
 
365
    $self->{windtype}      = undef;             # the wind speed type (knots/meterpersecond/kilometersperhour)
 
366
    $self->{windvar}       = undef;             # the wind variation
235
367
    $self->{visibility}    = undef;             # visibility information
236
368
    $self->{runway}        = undef;             # runway visibility
237
369
    $self->{weather}       = [ ];               # current weather conditions
238
370
    $self->{sky}           = [ ];               # sky conditions (cloud cover)
239
371
    $self->{temp_dew}      = undef;             # temp and dew pt.
240
 
    $self->{alt}           = undef;             # altimeter setting (Hg)
241
 
    $self->{alt_hp}        = undef;             # altimeter setting (hPa)
 
372
    $self->{alt}           = undef;             # altimeter setting
 
373
    $self->{pressure}      = undef;             # pressure (HPa)
242
374
    $self->{slp}           = undef;             # sea level pressure
243
375
    $self->{remarks}       = [ ];               # remarks
244
376
 
360
492
    my @toks = @{$self->{tokens}};      # copy tokens array...
361
493
 
362
494
    my $tok;
363
 
    my $in_remarks = 0;                 # started processing remarks
364
495
 
365
496
    ## This is a semi-brute-force way of doing things, but the amount
366
497
    ## of data is relatively small, so it shouldn't be a big deal.
368
499
    ## Ideally, I'd have it skip checks for items which have been
369
500
    ## found, but that would make this more "linear" and I'd remove
370
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
        my $expect_type = 0;
 
509
        my $expect_site = 1;
 
510
        my $expect_datetime = 2;
 
511
        my $expect_modifier = 3;
 
512
        my $expect_wind = 4;
 
513
        my $expect_visibility = 5;
 
514
        my $expect_runwayvisual = 6;
 
515
        my $expect_presentweather = 7;
 
516
        my $expect_clouds = 8;
 
517
        my $expect_temperature = 9;
 
518
        my $expect_pressure = 10;
 
519
        my $expect_recentweather = 11;
 
520
        my $expect_remarks = 12;
 
521
        my $expect_usremarks = 13;
 
522
 
 
523
        my $parsestate = $expect_type;
 
524
 
 
525
        # windtypes
 
526
        
 
527
        my $wt_knots = 1;
 
528
        my $wt_mps = 2;
 
529
        my $wt_kph = 3;
371
530
 
372
531
    ## Assume standard report by default
373
532
 
376
535
 
377
536
    while (defined($tok = shift(@toks))) ## as long as there are tokens
378
537
    {
379
 
        print "trying to match [$tok]\n" if $self->{debug};
 
538
        print "trying to match [$tok] state is $parsestate\n" if $self->{debug};
380
539
 
381
540
        ##
382
541
        ## is it a report type?
383
542
        ##
384
543
 
385
 
        if (($tok =~ /METAR/i) or ($tok =~ /SPECI/i))
 
544
        if (($parsestate == $expect_type) and ($tok =~ /(METAR|SPECI)/i))
386
545
        {
387
546
            $self->{type} = $tok;
388
547
 
395
554
                $self->{TYPE} = "Special Weather Report";
396
555
            }
397
556
            print "[$tok] is a report type.\n" if $self->{debug};
 
557
                        $parsestate = $expect_site;
398
558
            next;
399
559
        }
400
560
 
402
562
        ## is is a site ID?
403
563
        ##
404
564
 
405
 
        elsif ($tok =~ /^[A-Z]{4,4}$/ && !$self->{site})
 
565
        elsif (($parsestate <= $expect_site) and ($tok =~ /([A-Z]{4}|K[A-Z0-9]{3})/))
406
566
        {
407
567
            $self->{site} = $tok;
408
568
            print "[$tok] is a site ID.\n" if $self->{debug};
 
569
                        $parsestate = $expect_datetime;
409
570
            next;
410
571
        }
411
572
 
413
574
        ## is it a date/time?
414
575
        ##
415
576
 
416
 
        elsif ($tok =~ /\d{6,6}Z/i)
 
577
        elsif (($parsestate == $expect_datetime) and ($tok =~ /\d{6,6}Z/i))
417
578
        {
418
579
            $self->{date_time} = $tok;
419
580
            print "[$tok] is a date/time.\n" if $self->{debug};
 
581
                        $parsestate = $expect_modifier;
420
582
            next;
421
583
 
422
584
 
426
588
        ## is it a report modifier?
427
589
        ##
428
590
 
429
 
        elsif (($tok =~ /AUTO/i) or ($tok =~ /COR/i))
 
591
        elsif (($parsestate == $expect_modifier) and ($tok =~ /AUTO|COR|CC[A-Z]/i))
430
592
        {
431
593
            $self->{modifier} = $tok;
432
594
            print "[$tok] is a report modifier.\n" if $self->{debug};
433
 
            next;
434
 
        }
435
 
 
436
 
        ##
437
 
        ## is it wind information?
438
 
        ##
439
 
 
440
 
        elsif ($tok =~ /.*?KT$/i)
 
595
                        $parsestate = $expect_wind;
 
596
            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
            $self->{wind} = $tok;
 
610
                        $self->{windtype} = $wt_knots;
 
611
            print "[$tok] is wind information in knots.\n" if $self->{debug};
 
612
                        $parsestate = $expect_wind; # stay in wind, it can have variation
 
613
            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$/))
441
622
        {
442
623
            $self->{wind} = $tok;
443
624
            print "[$tok] is wind information.\n" if $self->{debug};
444
 
            next;
445
 
        }
446
 
 
447
 
        ##
448
 
        ## is it variable wind information?
449
 
        ##
450
 
        
451
 
        elsif ($tok =~ /^\d\d\dV\d\d\d$/i)
452
 
        {
453
 
            $self->{vrbwind} = $tok;
454
 
            print "[$tok] is variable wind information.\n" if $self->{debug};
455
 
            next;
456
 
        }
457
 
 
458
 
        ##
459
 
        ## is it visibility information?
460
 
        ##
461
 
 
462
 
        elsif ($tok =~ /.*?SM$/i)
 
625
                        $self->{windtype} = $wt_mps;
 
626
                        $parsestate = $expect_wind; # stay in wind, it can have variation
 
627
            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
                        $self->{windvar} = $tok;
 
637
                        print "[$tok] is wind variation information.\n" if $self->{debug};
 
638
                        $parsestate = $expect_visibility;
 
639
                        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
                        print "[$tok] is missing wind information.\n" if $self->{debug};
 
648
                        $parsestate = $expect_visibility;
 
649
                        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
                        $self->{visibility} = $tok;
 
659
            print "[$tok] is numerical visibility information.\n" if $self->{debug};
 
660
                        $parsestate = $expect_visibility;
 
661
            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
                        $self->{visibility} = $tok;
 
669
            print "[$tok] is automatic numerical visibility information.\n" if $self->{debug};
 
670
                        $parsestate = $expect_visibility;
 
671
            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))
463
679
        {
464
680
            $self->{visibility} = $tok;
465
 
            print "[$tok] is visibility information.\n" if $self->{debug};
 
681
            print "[$tok] is statute miles visibility information.\n" if $self->{debug};
 
682
                        $parsestate = $expect_visibility;
466
683
            next;
467
684
        }
468
685
 
469
 
                ##
470
 
                ## does it say CAVOK? (ceiling and visibility ok)
471
 
                ##
472
 
                
473
 
                elsif ($tok =~ /^CAVOK/i)
474
 
                {
475
 
                        $self->{visibility} = $tok;
476
 
                        print "[$tok] is visibility information, too.\n" if $self->{debug};
477
 
                        next;
478
 
                }
479
 
 
480
686
        ##
481
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
                ##                           ^^^^^^^
482
692
        ##
483
693
 
484
 
        elsif ($tok =~ /^\d$/)
 
694
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and  ($tok =~ /^\d$/))
485
695
        {
486
696
            $tok .= " " . shift(@toks);
487
697
            $self->{visibility} = $tok;
488
 
            print "[$tok is multi-part visibility information.\n" if $self->{debug};
 
698
            print "[$tok] is multi-part visibility information.\n" if $self->{debug};
 
699
                        $parsestate = $expect_visibility;
489
700
            next;
490
701
        }
491
702
 
 
703
                ## visibility modifier
 
704
 
 
705
                elsif (($parsestate == $expect_visibility) and ($tok =~ /^\d{4}(N|S|E|W|NE|NW|SE|SW)$/))
 
706
                {
 
707
            print "[$tok] is a visibility modifier.\n" if $self->{debug};
 
708
            next;
 
709
                }
 
710
 
492
711
        ##
493
712
        ## is it runway visibility info?
494
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/
495
718
 
496
 
        elsif ($tok =~ /R.*?FT$/i)
 
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))
497
720
        {
498
 
            $self->{runway} = $tok;
 
721
            push (@{$self->{RUNWAY}},$tok);
499
722
            print "[$tok] is runway visual information.\n" if $self->{debug};
 
723
                        $parsestate = $expect_runwayvisual;
 
724
                        # there can be multiple runways, so stay at this state
500
725
            next;
501
726
        }
502
727
 
504
729
        ## is it current weather info?
505
730
        ##
506
731
 
507
 
        elsif ($tok =~ /^(-|\+)?(VC)?($_weather_types_pat)+/i)
 
732
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_clouds) and ($tok =~ /^(-|\+)?(VC)?($_weather_types_pat)+/i))
508
733
        {
509
734
            my $engl = "";
510
735
            my $qual = $1;
546
771
            $engl =~ s/\s\s/ /gio;
547
772
 
548
773
            push(@{$self->{WEATHER}},$engl);
549
 
 
550
774
            push(@{$self->{weather}},$tok);
551
775
            print "[$tok] is current weather.\n" if $self->{debug};
 
776
                        $parsestate = $expect_presentweather;
 
777
                        # there can be multiple current weather types, so stay at this state
552
778
            next;
553
779
        }
554
780
 
 
781
                ##
 
782
                ## special case: CAVOK
 
783
                ##
 
784
                
 
785
                elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok eq 'CAVOK' ))
 
786
                {
 
787
            push(@{$self->{sky}},$tok);
 
788
            push(@{$self->{SKY}}, "Sky Clear");
 
789
            push(@{$self->{weather}},$tok);
 
790
            push(@{$self->{WEATHER}},"No significant weather");
 
791
                        $self->{visibility} = '9999';
 
792
                        $parsestate = $expect_temperature;
 
793
                        next;
 
794
                }
 
795
 
555
796
        ##
556
797
        ## is it sky conditions (clear)?
557
798
        ##
558
799
 
559
 
        elsif ( $tok eq "SKC" || $tok eq "CLR" )
 
800
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /SKC|CLR/ ))
560
801
        {
561
802
            push(@{$self->{sky}},$tok);
562
803
            push(@{$self->{SKY}}, "Sky Clear");
 
804
            print "[$tok] is a sky condition.\n" if $self->{debug};
 
805
                        $parsestate = $expect_clouds;
 
806
                        next;
563
807
        }
564
808
 
565
809
        ##
566
810
        ## is it sky conditions (clouds)?
567
811
        ##
 
812
                ## sky conditions can end with ///
568
813
 
569
 
        elsif ( $tok =~ /^(FEW|SCT|BKN|OVC|SKC|CLR)(\d\d\d)?(CB|TCU)?$/i)
 
814
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^(FEW|SCT|BKN|OVC)(\d\d\d)?(CB|TCU)?\/*$/i))
570
815
        {
571
816
            push(@{$self->{sky}},$tok);
572
817
            my $engl = "";
593
838
 
594
839
            push(@{$self->{SKY}}, $engl);
595
840
            print "[$tok] is a sky condition.\n" if $self->{debug};
 
841
                        $parsestate = $expect_clouds;
 
842
                        # clouds DO repeat. a lot ;)
596
843
            next;
597
844
        }
598
845
 
 
846
                ##
 
847
                ## auto detected cloud conditions
 
848
                ##
 
849
 
 
850
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^(NSC|NCD)$/ )){
 
851
            my $engl = "";
 
852
 
 
853
            $engl = $_sky_types{$tok};
 
854
            push(@{$self->{SKY}}, $engl);
 
855
                        print "[$tok] is an automatic sky condition.\n" if $self->{debug};
 
856
                        $parsestate = $expect_temperature;
 
857
                        next;
 
858
                }
 
859
 
 
860
                ##
 
861
                ## Vertical visibility
 
862
                ##
 
863
 
 
864
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^VV\d+$/ )){
 
865
                        print "[$tok] is vertical visibility.\n" if $self->{debug};
 
866
                        $parsestate = $expect_temperature;
 
867
                        next;
 
868
                }
 
869
 
599
870
        ##
600
871
        ## is it temperature and dew point info?
601
872
        ##
602
873
 
603
 
        elsif ($tok =~ /(M?\d\d)\/(M?\d\d)/i)
 
874
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_pressure) and ($tok =~ /^(M?\d\d)\/(M?\d{0,2})/i))
604
875
        {
605
876
            next if $self->{temp_dew};
606
877
            $self->{temp_dew} = $tok;
611
882
            $self->{DEW_C} =~ s/^M/-/;
612
883
 
613
884
            print "[$tok] is temperature/dew point information.\n" if $self->{debug};
 
885
                        $parsestate = $expect_pressure;
614
886
            next;
615
887
        }
616
888
 
617
889
        ##
618
 
        ## is it an altimeter setting? (inches in mercury)
 
890
        ## is it an altimeter setting?
619
891
        ##
620
892
 
621
 
        elsif (!$in_remarks && $tok =~ /^A(\d\d)(\d\d)$/i)
 
893
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^A(\d\d)(\d\d)$/i))
622
894
        {
623
895
            $self->{alt} = $tok;
624
896
            $self->{ALT} = "$1.$2";
 
897
 
 
898
                        # inches Hg pressure. How imperial can you get
 
899
                        # conversion using 'units'
 
900
 
 
901
                        $self->{pressure} = 33.863886 * $self->{ALT};
 
902
 
625
903
            print "[$tok] is an altimeter setting.\n" if $self->{debug};
 
904
                        $parsestate = $expect_recentweather;
626
905
            next;
627
906
        }
628
 
                
629
 
                ##
630
 
                ## is it an altimeter setting? (hectopascals)
631
 
                
632
 
                elsif (!$in_remarks && $tok =~ /^Q(\d\d\d\d)$/i)
 
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))
633
913
                {
634
 
                        $self->{alt_hp} = $tok;
635
 
                        $self->{ALT_HP} = $1;
636
 
                        print "[$tok] is an altimeter setting in hectopascals.\n"
637
 
                                if $self->{debug};
638
 
                        next;
639
 
                }
 
914
                        $self->{pressure} = $1;
 
915
 
 
916
                        $self->{ALT} = 0.029529983*$self->{pressure};
 
917
                        print "[$tok] is an air pressure.\n" if $self->{debug};
 
918
                        $parsestate = $expect_recentweather;
 
919
                        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
                        print "[$tok] is recent significant weather.\n" if $self->{debug};
 
928
                        $parsestate = $expect_remarks;
 
929
                        next;
 
930
                }
 
931
 
 
932
                ##
 
933
                ## euro type trend?
 
934
                ##
 
935
 
 
936
                elsif (($parsestate >= $expect_modifier) and ($tok =~ /^$_trend_types_pat/)){
 
937
                        print "[$tok] is a trend.\n" if $self->{debug};
 
938
                        $parsestate = $expect_remarks;
 
939
                        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
            push(@{$self->{remarks}},$tok);
 
949
            print "[$tok] is a (US type) remark.\n" if $self->{debug};
 
950
                        $parsestate  = $expect_usremarks;
 
951
            next;
 
952
        }
640
953
 
641
954
        ##
642
955
        ## automatic station type?
643
956
        ##
644
957
 
645
 
        elsif ($in_remarks && $tok =~ /^A(O\d)$/i)
 
958
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^A(O\d)$/i))
646
959
        {
647
960
            $self->{autostationtype} = $tok;
648
961
            $self->{AUTO_STATIONTYPE} = $1;
651
964
        }
652
965
 
653
966
        ##
654
 
        ## remarks?
655
 
        ##
656
 
 
657
 
        elsif ($tok =~ /^RMK$/i)
658
 
        {
659
 
            push(@{$self->{remarks}},$tok);
660
 
            $in_remarks = 1;
661
 
            print "[$tok] is a remark.\n" if $self->{debug};
662
 
            next;
663
 
        }
664
 
 
665
 
        ##
666
967
        ## sea level pressure
667
968
        ##
668
969
 
669
 
        elsif ($tok =~ /^SLP(\d+)/i)
 
970
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^SLP(\d+)/i))
670
971
        {
671
972
            $self->{slp} = $tok;
672
973
            $self->{SLP} = "$1 mb";
678
979
        ## sea level pressure not available
679
980
        ##
680
981
 
681
 
        elsif ($tok eq "SLPNO")
 
982
        elsif (($parsestate == $expect_usremarks) and ($tok eq "SLPNO"))
682
983
        {
683
984
            $self->{slp} = "SLPNO";
684
985
            $self->{SLP} = "not available";
690
991
        ## hourly precipitation
691
992
        ##
692
993
 
693
 
        elsif ($tok =~ /^P(\d\d\d\d)$/i)
 
994
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^P(\d\d\d\d)$/i))
694
995
        {
695
996
            $self->{hourlyprecip} = $tok;
696
997
 
705
1006
        ## weather begin/end times
706
1007
        ##
707
1008
 
708
 
        elsif ($tok =~ /^($_weather_types_pat)([BE\d]+)$/i)
 
1009
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^($_weather_types_pat)([BE\d]+)$/i))
709
1010
        {
710
1011
            my $engl = "";
711
1012
            my $times = $2;
732
1033
        ## remarks for significant cloud types
733
1034
        ##
734
1035
 
735
 
        elsif ($in_remarks && ($tok eq "CB" || $tok eq "TCU"))
 
1036
        elsif (($parsestate >= $expect_recentweather) and ($tok eq "CB" || $tok eq "TCU"))
736
1037
        {
737
1038
            push(@{$self->{sigclouds}}, $tok);
738
1039
 
741
1042
            } elsif ( $tok eq "TCU" ) {
742
1043
                push(@{$self->{SIGCLOUDS}}, "Towering Cumulus");
743
1044
            }
 
1045
                        $parsestate = $expect_usremarks;
744
1046
        }
745
1047
 
746
1048
        ##
747
1049
        ## hourly temp/dewpoint
748
1050
        ##
749
1051
 
750
 
        elsif ($tok =~ /^T(\d)(\d\d)(\d)(\d)(\d\d)(\d)$/i)
 
1052
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^T(\d)(\d\d)(\d)(\d)(\d\d)(\d)$/i))
751
1053
        {
752
1054
            $self->{hourlytempdew} = $tok;
753
1055
            if ( $1 == 1 ) {
769
1071
        ## unknown, not in remarks yet
770
1072
        ##
771
1073
 
772
 
        elsif (!$in_remarks)
 
1074
        elsif ($parsestate < $expect_remarks)
773
1075
        {
774
1076
            push(@{$self->{unknown}},$tok);
775
1077
            push(@{$self->{UNKNOWN}},$tok);
776
 
            print "[$tok] is unknown.\n" if $self->{debug};
 
1078
            print "[$tok] is unexpected at this state.\n" if $self->{debug};
777
1079
            next;
778
1080
        }
779
1081
 
806
1108
    ## Okay, wind finally gets interesting.
807
1109
    ##
808
1110
 
809
 
    {
 
1111
    if ( defined $self->{wind} )
 
1112
        {
810
1113
        my $wind = $self->{wind};
811
1114
        my $dir_deg  = substr($wind,0,3);
812
1115
        my $dir_eng = "";
 
1116
                my $dir_abb = "";
813
1117
 
814
1118
        # Check for wind direction
815
1119
        if ($dir_deg =~ /VRB/i) {
817
1121
        } else {
818
1122
            if      ($dir_deg < 15) {
819
1123
                $dir_eng = "North";
 
1124
                                $dir_abb = "N";
820
1125
            } elsif ($dir_deg < 30) {
821
1126
                $dir_eng = "North/Northeast";
 
1127
                                $dir_abb = "NNE";
822
1128
            } elsif ($dir_deg < 60) {
823
1129
                $dir_eng = "Northeast";
 
1130
                                $dir_abb = "NE";
824
1131
            } elsif ($dir_deg < 75) {
825
1132
                $dir_eng = "East/Northeast";
 
1133
                                $dir_abb = "ENE";
826
1134
            } elsif ($dir_deg < 105) {
827
1135
                $dir_eng = "East";
 
1136
                                $dir_abb = "E";
828
1137
            } elsif ($dir_deg < 120) {
829
1138
                $dir_eng = "East/Southeast";
 
1139
                                $dir_abb = "ESE";
830
1140
            } elsif ($dir_deg < 150) {
831
1141
                $dir_eng = "Southeast";
 
1142
                                $dir_abb = "SE";
832
1143
            } elsif ($dir_deg < 165) {
833
1144
                $dir_eng = "South/Southeast";
 
1145
                                $dir_abb = "SSE";
834
1146
            } elsif ($dir_deg < 195) {
835
1147
                $dir_eng = "South";
 
1148
                                $dir_abb = "S";
836
1149
            } elsif ($dir_deg < 210) {
837
1150
                $dir_eng = "South/Southwest";
 
1151
                                $dir_abb = "SSW";
838
1152
            } elsif ($dir_deg < 240) {
839
1153
                $dir_eng = "Southwest";
 
1154
                                $dir_abb = "SW";
840
1155
            } elsif ($dir_deg < 265) {
841
1156
                $dir_eng = "West/Southwest";
 
1157
                                $dir_abb = "WSW";
842
1158
            } elsif ($dir_deg < 285) {
843
1159
                $dir_eng = "West";
 
1160
                                $dir_abb = "W";
844
1161
            } elsif ($dir_deg < 300) {
845
1162
                $dir_eng = "West/Northwest";
 
1163
                                $dir_abb = "WNW";
846
1164
            } elsif ($dir_deg < 330) {
847
1165
                $dir_eng = "Northwest";
 
1166
                                $dir_abb = "NW";
848
1167
            } elsif ($dir_deg < 345) {
849
1168
                $dir_eng = "North/Northwest";
 
1169
                                $dir_abb = "NNW";
850
1170
            } else {
851
1171
                $dir_eng = "North";
 
1172
                                $dir_abb = "N";
852
1173
            }
853
1174
        }
854
1175
 
855
 
        $wind =~ /...(\d\d\d?)/o;
856
 
        my $kts_speed = $1;
857
 
        my $mph_speed = $kts_speed * 1.1508;
858
 
        my $kts_gust = "";
859
 
        my $mph_gust = "";
860
 
 
861
 
        if ($wind =~ /.{5,6}G(\d\d\d?)/o) {
862
 
            $kts_gust = $1;
863
 
            $mph_gust = $kts_gust * 1.1508;
864
 
        }
 
1176
                my $kts_speed = undef;
 
1177
                my $mph_speed = undef;
 
1178
 
 
1179
                my $kts_gust = "";
 
1180
                my $mph_gust = "";
 
1181
 
 
1182
                # parse knots
 
1183
 
 
1184
                if ($self->{windtype} == $wt_knots){
 
1185
                        $wind =~ /...(\d\d\d?)/o;
 
1186
                        $kts_speed = $1;
 
1187
                        $mph_speed = $kts_speed * 1.1508;
 
1188
 
 
1189
 
 
1190
                        if ($wind =~ /.{5,6}G(\d\d\d?)/o) {
 
1191
                                $kts_gust = $1;
 
1192
                                $mph_gust = $kts_gust * 1.1508;
 
1193
                        }
 
1194
                # else: parse meters/second
 
1195
                } elsif ($self->{windtype} == $wt_mps){
 
1196
                        $wind=~ /...(\d\d\d?)/o;
 
1197
                        my $mps_speed = $1;
 
1198
                        $kts_speed = $mps_speed * 1.9438445; # units
 
1199
                        $mph_speed = $mps_speed * 2.2369363;
 
1200
                        if ($wind =~ /\d{5,6}G(\d\d\d?)/o) {
 
1201
                                my $mps_gust = $1;
 
1202
                                $kts_gust = $mps_gust * 1.9438445;
 
1203
                                $mph_gust = $mps_gust * 2.2369363;
 
1204
                        }
 
1205
                } else {
 
1206
                        warn "Geo::METAR Parser error: unknown windtype\n";
 
1207
                }
865
1208
 
866
1209
        $self->{WIND_KTS} = $kts_speed;
867
1210
        $self->{WIND_MPH} = $mph_speed;
871
1214
 
872
1215
        $self->{WIND_DIR_DEG} = $dir_deg;
873
1216
        $self->{WIND_DIR_ENG} = $dir_eng;
 
1217
        $self->{WIND_DIR_ABB} = $dir_abb;
874
1218
 
875
1219
    }
876
1220
 
877
 
    ##
878
 
    ## Variable wind information
879
 
    ##
 
1221
        ##
 
1222
        ## wind variation
 
1223
        ##
880
1224
 
 
1225
        if (defined $self->{windvar})
881
1226
        {
882
 
                if ($self->{vrbwind}) {
883
 
                        my $vrbwind = $self->{vrbwind};
884
 
                        my $vrbwind_deg_1 = substr($vrbwind,0,3);
885
 
                        my $vrbwind_deg_2 = substr($vrbwind,4,3);
886
 
                        my $vrbwind_eng_1 = "";
887
 
                        my $vrbwind_eng_2 = "";
888
 
 
889
 
                        if ($vrbwind_deg_1 < 15) { $vrbwind_eng_1 = "North"; }
890
 
                        elsif ($vrbwind_deg_1 < 30) { $vrbwind_eng_1 = "North/Northeast"; }
891
 
                        elsif ($vrbwind_deg_1 < 60) { $vrbwind_eng_1 = "Northeast"; }
892
 
                        elsif ($vrbwind_deg_1 < 75) { $vrbwind_eng_1 = "East/Northeast"; }
893
 
                        elsif ($vrbwind_deg_1 < 105) { $vrbwind_eng_1 = "East"; }
894
 
                        elsif ($vrbwind_deg_1 < 120) { $vrbwind_eng_1 = "East/Southeast"; }
895
 
                        elsif ($vrbwind_deg_1 < 150) { $vrbwind_eng_1 = "Southeast"; }
896
 
                        elsif ($vrbwind_deg_1 < 165) { $vrbwind_eng_1 = "South/Southeast"; }
897
 
                        elsif ($vrbwind_deg_1 < 195) { $vrbwind_eng_1 = "South"; }
898
 
                        elsif ($vrbwind_deg_1 < 210) { $vrbwind_eng_1 = "South/Southwest"; }
899
 
                        elsif ($vrbwind_deg_1 < 240) { $vrbwind_eng_1 = "Southwest"; }
900
 
                        elsif ($vrbwind_deg_1 < 265) { $vrbwind_eng_1 = "West/Southwest"; }
901
 
                        elsif ($vrbwind_deg_1 < 285) { $vrbwind_eng_1 = "West"; }
902
 
                        elsif ($vrbwind_deg_1 < 300) { $vrbwind_eng_1 = "West/Northwest"; }
903
 
                        elsif ($vrbwind_deg_1 < 330) { $vrbwind_eng_1 = "Northwest"; }
904
 
                        elsif ($vrbwind_deg_1 < 345) { $vrbwind_eng_1 = "North/Northwest"; }
905
 
                        else { $vrbwind_eng_1 = "North"; }
906
 
 
907
 
                        if ($vrbwind_deg_2 < 15) { $vrbwind_eng_2 = "North"; }
908
 
                        elsif ($vrbwind_deg_2 < 30) { $vrbwind_eng_2 = "North/Northeast"; }
909
 
                        elsif ($vrbwind_deg_2 < 60) { $vrbwind_eng_2 = "Northeast"; }
910
 
                        elsif ($vrbwind_deg_2 < 75) { $vrbwind_eng_2 = "East/Northeast"; }
911
 
                        elsif ($vrbwind_deg_2 < 105) { $vrbwind_eng_2 = "East"; }
912
 
                        elsif ($vrbwind_deg_2 < 120) { $vrbwind_eng_2 = "East/Southeast"; }
913
 
                        elsif ($vrbwind_deg_2 < 150) { $vrbwind_eng_2 = "Southeast"; }
914
 
                        elsif ($vrbwind_deg_2 < 165) { $vrbwind_eng_2 = "South/Southeast"; }
915
 
                        elsif ($vrbwind_deg_2 < 195) { $vrbwind_eng_2 = "South"; }
916
 
                        elsif ($vrbwind_deg_2 < 210) { $vrbwind_eng_2 = "South/Southwest"; }
917
 
                        elsif ($vrbwind_deg_2 < 240) { $vrbwind_eng_2 = "Southwest"; }
918
 
                        elsif ($vrbwind_deg_2 < 265) { $vrbwind_eng_2 = "West/Southwest"; }
919
 
                        elsif ($vrbwind_deg_2 < 285) { $vrbwind_eng_2 = "West"; }
920
 
                        elsif ($vrbwind_deg_2 < 300) { $vrbwind_eng_2 = "West/Northwest"; }
921
 
                        elsif ($vrbwind_deg_2 < 330) { $vrbwind_eng_2 = "Northwest"; }
922
 
                        elsif ($vrbwind_deg_2 < 345) { $vrbwind_eng_2 = "North/Northwest"; }
923
 
                        else { $vrbwind_eng_2 = "North"; }
924
 
 
925
 
                        push @{$self->{WIND_VAR_DEG}}, $vrbwind_deg_1;
926
 
                        push @{$self->{WIND_VAR_DEG}}, $vrbwind_deg_2;
927
 
                        push @{$self->{WIND_VAR_ENG}}, $vrbwind_eng_1;
928
 
                        push @{$self->{WIND_VAR_ENG}}, $vrbwind_eng_2;
 
1227
                if ($self->{windvar} =~ /^(\d\d\d)V(\d\d\d)$/){
 
1228
                        $self->{WIND_VAR} = "Varying between $1 and $2";
 
1229
                        $self->{WIND_VAR_1} = $1;
 
1230
                        $self->{WIND_VAR_2} = $2;
929
1231
                }
930
1232
        }
931
1233
 
934
1236
    ##
935
1237
 
936
1238
    {
937
 
                if ($self->{visibility}) {
938
 
           my $vis = $self->{visibility};
939
 
           $vis =~ s/SM$//oi;                              # nuke the "SM"
940
 
                   if ($vis =~ /^CAVOK$/i) {
941
 
                       $self->{VISIBILITY} = "Ceiling and visibility OK";
942
 
                   }
943
 
           elsif ($vis =~ /M(\d\/\d)/o) {
944
 
               $self->{VISIBILITY} = "Less than $1 statute miles";
945
 
           } else {
946
 
               $self->{VISIBILITY} = $vis . " Statute Miles";
947
 
           } # end if
 
1239
        my $vis = $self->{visibility};
 
1240
                # test for statute miles
 
1241
                if ($vis =~ /SM$/){
 
1242
                        $vis =~ s/SM$//oi;                              # nuke the "SM"
 
1243
                        if ($vis =~ /M(\d\/\d)/o) {
 
1244
                                $self->{VISIBILITY} = "Less than $1 statute miles";
 
1245
                        } else {
 
1246
                                $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
                        $self->{VISIBILITY} = "$1 meters non-directional visibility";
 
1251
                } else {
 
1252
                        $self->{VISIBILITY} = $vis . " meters";
948
1253
                }
949
1254
    }
950
1255
 
951
 
        ##
952
 
        ## Convert ALT to ALT_HP or vice versa
953
 
        ##
954
 
        
955
 
        {
956
 
                if ($self->{ALT} && !$self->{ALT_HP}) {
957
 
                        my $alt = $self->{ALT};
958
 
                        $alt = $alt * 1.33;                     # mmHg to hPa
959
 
                        $self->{ALT_HP} = $alt;
960
 
                }
961
 
                elsif (!$self->{ALT} && $self->{ALT_HP}) {
962
 
                        my $alt = $self->{ALT_HP};
963
 
                        $alt = $alt * 0.75;                     # hPa to mmHg
964
 
                        $self->{ALT} = $alt;
965
 
                }
966
 
        }
967
 
 
968
1256
    ##
969
1257
    ## Calculate F temps for all C temps
970
1258
    ##
975
1263
        {
976
1264
            my $fkey = $1 . "_F";
977
1265
 
978
 
            next unless defined $self->{$key};
 
1266
            next unless defined $self->{$key} && $self->{$key};
979
1267
 
980
1268
            $self->{$fkey} = sprintf("%.1f", (($self->{$key} * (9/5)) + 32));
981
1269
        }
982
1270
    }
 
1271
 
 
1272
        # join the runway group
 
1273
        
 
1274
        $self->{runway} = join(', ' , @{$self->{RUNWAY}});
 
1275
        
983
1276
}
984
1277
 
985
1278
##
1022
1315
{
1023
1316
    my $self = shift;
1024
1317
 
1025
 
    print "METAR dump follows.\n\n";
 
1318
    print "Modified METAR dump follows.\n\n";
1026
1319
 
1027
1320
    print "type: $self->{type}\n";
1028
1321
    print "site: $self->{site}\n";
1029
1322
    print "date_time: $self->{date_time}\n";
1030
1323
    print "modifier: $self->{modifier}\n";
1031
1324
    print "wind: $self->{wind}\n";
1032
 
    print "variable wind: $self->{vrbwind}\n";
1033
1325
    print "visibility: $self->{visibility}\n";
1034
1326
    print "runway: $self->{runway}\n";
1035
1327
    print "weather: " . join(', ', @{$self->{weather}}) . "\n";
1036
1328
    print "sky: " . join(', ', @{$self->{sky}}) . "\n";
1037
1329
    print "temp_dew: $self->{temp_dew}\n";
1038
 
    print "alt: $self->{alt}\n";
1039
 
        print "alt_hp: $self->{alt_hp}\n";
 
1330
    print "alt: $self->{ALT}\n";
1040
1331
    print "slp: $self->{slp}\n";
1041
1332
    print "remarks: " . join (', ', @{$self->{remarks}}) . "\n";
1042
1333
    print "\n";
1080
1371
METAR reports are available on-line, thanks to the National Weather Service.
1081
1372
Since reading the METAR format isn't easy for non-pilots, these reports are
1082
1373
relatively useles to the common man who just wants a quick glace at the
1083
 
weather.
 
1374
weather. This module tries to parse the METAR reports so the data can be
 
1375
used to create readable weather reports and/or process the data in
 
1376
applications.
1084
1377
 
1085
1378
=head1 USAGE
1086
1379
 
1089
1382
Here is how you I<might> use the Geo::METAR module.
1090
1383
 
1091
1384
One use that I have had for this module is to query the NWS METAR page
1092
 
(using the LWP modules) at:
1093
 
 
1094
 
I<http://weather.noaa.gov/cgi-bin/mgetmetar.pl?cccc=KFDY>
1095
 
 
1096
 
to get an up-to-date METAR. Then, I scan thru the output, looking for what 
1097
 
looks like a METAR string (that's not hard in Perl). Oh, KFDY can be any site
 
1385
(using the LWP modules) at
 
1386
http://weather.noaa.gov/cgi-bin/mgetmetar.pl?cccc=EHSB to get an
 
1387
up-to-date METAR. Then, I scan thru the output, looking for what looks
 
1388
like a METAR string (that's not hard in Perl). Oh, EHSB can be any site
1098
1389
location code where there is a reporting station.
1099
1390
 
1100
1391
I then pass the METAR into this module and get the info I want. I can
1101
 
then update my home page with the current temperature, sky conditions, or
1102
 
whatnot.
 
1392
then update my webcam page with the current temperature, sky conditions, or
 
1393
whatnot. See for yourself at http://webcam.idefix.net/
 
1394
 
 
1395
See the BUGS section for a remark about multiple passes with the same
 
1396
Geo::METAR object.
1103
1397
 
1104
1398
=head2 Functions
1105
1399
 
1106
 
The following functions are defined in the AcctInfo module. Most of
 
1400
The following functions are defined in the METAR module. Most of
1107
1401
them are I<public>, meaning that you're supposed to use
1108
1402
them. Some are I<private>, meaning that you're not supposed to use
1109
1403
them -- but I won't stop you. Assume that functions are I<public>
1175
1469
This section lists those variables and what they represent.
1176
1470
 
1177
1471
If you call B<dump()>, you'll find that it spits all of these
1178
 
out in roughly this order, too.
 
1472
out.
1179
1473
 
1180
1474
=over
1181
1475
 
1189
1483
 
1190
1484
=item TYPE
1191
1485
 
1192
 
Report type: "METAR or SPECI".
 
1486
Report type in English ("Routine Weather Report" or "Special Weather Report")
1193
1487
 
1194
1488
=item SITE
1195
1489
 
1197
1491
 
1198
1492
=item DATE
1199
1493
 
1200
 
The date on which the report was issued.
 
1494
The date (just the day of the month) on which the report was issued.
1201
1495
 
1202
1496
=item TIME
1203
1497
 
1209
1503
 
1210
1504
=item WIND_DIR_ENG
1211
1505
 
1212
 
The current wind direction in English (Southwest, East, North, etc.)
 
1506
The current wind direction in english (Southwest, East, North, etc.)
 
1507
 
 
1508
=item WIND_DIR_ABB
 
1509
 
 
1510
The current wind direction in abbreviated english (S, E, N, etc.)
1213
1511
 
1214
1512
=item WIND_DIR_DEG
1215
1513
 
1216
1514
The current wind direction in degrees.
1217
1515
 
1218
 
=item WIND_VAR_ENG
1219
 
 
1220
 
Variable wind direction in English.
1221
 
 
1222
 
=item WIND_VAR_DEG
1223
 
 
1224
 
Variable wind direction in degrees.
1225
 
 
1226
1516
=item WIND_KTS
1227
1517
 
1228
1518
The current wind speed in Knots.
1239
1529
 
1240
1530
The current wind gusting speed in Miles Per Hour.
1241
1531
 
 
1532
=item WIND_VAR
 
1533
 
 
1534
The wind variation in English
 
1535
 
 
1536
=item WIND_VAR_1
 
1537
 
 
1538
The first wind variation direction
 
1539
 
 
1540
=item WIND_VAR_2
 
1541
 
 
1542
The second wind variation direction
 
1543
 
1242
1544
=item VISIBILITY
1243
1545
 
1244
1546
Visibility information.
1253
1555
 
1254
1556
=item WEATHER
1255
1557
 
1256
 
Current weather.
 
1558
Current weather (array)
 
1559
 
 
1560
=item WEATHER_LOG
 
1561
 
 
1562
Current weather log (array)
1257
1563
 
1258
1564
=item SKY
1259
1565
 
1260
 
Current sky conditions.
 
1566
Current cloud cover (array)
1261
1567
 
1262
1568
=item TEMP_C
1263
1569
 
1265
1571
 
1266
1572
=item TEMP_F
1267
1573
 
1268
 
Temperature in Farenheit.
 
1574
Temperature in Fahrenheit.
1269
1575
 
1270
 
=item C_DEW
 
1576
=item DEW_C
1271
1577
 
1272
1578
Dew point in Celsius.
1273
1579
 
1274
 
=item F_DEW
1275
 
 
1276
 
Dew point in Farenheit.
 
1580
=item DEW_F
 
1581
 
 
1582
Dew point in Fahrenheit.
 
1583
 
 
1584
=item HOURLY_TEMP_F
 
1585
 
 
1586
Hourly current temperature, fahrenheit
 
1587
 
 
1588
=item HOURLY_TEMP_C
 
1589
 
 
1590
Hourly current temperature, celcius
 
1591
 
 
1592
=item HOURLY_DEW_F
 
1593
 
 
1594
Hourly dewpoint, fahrenheit
 
1595
 
 
1596
=item HOURLY_DEW_C
 
1597
 
 
1598
Hourly dewpoint, celcius
1277
1599
 
1278
1600
=item ALT
1279
1601
 
1280
1602
Altimeter setting (barometric pressure).
1281
1603
 
1282
 
=item ALT_HP
1283
 
 
1284
 
Altimeter setting in hectopascals.
1285
 
 
1286
1604
=item REMARKS
1287
1605
 
1288
1606
Any remarks in the report.
1296
1614
Older versions of this module were installed as "METAR" instaed of
1297
1615
"Geo::METAR"
1298
1616
 
1299
 
=head2 Adding a find() method.
1300
 
 
1301
 
I should add a function called find() which can be passed a big chunk
1302
 
of text (or a ref to one) and a site identifier. It will scan through
1303
 
the text and find the METAR. The result can be fed back into this
1304
 
module for processing.
1305
 
 
1306
 
That'd be cool, I think.
1307
 
 
1308
1617
=head1 BUGS
1309
1618
 
1310
 
There currently aren't any known BUGS (features which don't work as
1311
 
advetised). There are lacking features. See the TODO section for more
1312
 
on that.
 
1619
The Geo::METAR is only initialized once, which means you'll get left-over
 
1620
crud in variables when you call the metar() function twice.
 
1621
 
 
1622
What is an invalid METAR in one country is a standard one in the next.
 
1623
The standard is interpreted and used by meteorologists all over the world,
 
1624
with local variations. This means there will always be METARs that will
 
1625
trip the parser.
1313
1626
 
1314
1627
=head1 TODO
1315
1628
 
1318
1631
list before you submit a bug report or request a new feture. It might
1319
1632
already be on the TODO list.
1320
1633
 
1321
 
=head1 AUTHOR AND COPYRIGHT
1322
 
 
1323
 
Copyright 1997-2000, Jeremy D. Zawodny <Jeremy@Zawodny.com>
 
1634
=head1 AUTHORS AND COPYRIGHT
 
1635
 
 
1636
Copyright 1997-2000, Jeremy D. Zawodny <Jeremy [at] Zawodny.com>
 
1637
 
 
1638
Copyright 2007, Koos van den Hout <koos@kzdoos.xs4all.nl>
1324
1639
 
1325
1640
Geo::METAR is covered under the GNU Public License (GPL) version 2 or
1326
1641
later.
1327
1642
 
1328
1643
The Geo::METAR Web site is located at:
1329
1644
 
1330
 
  http://www.wcnet.org/~jzawodn/perl/Geo-METAR/
 
1645
  http://idefix.net/~koos/perl/Geo-METAR/
1331
1646
 
1332
1647
=head1 CREDITS
1333
1648
 
1334
 
In addition to my work on Geo::METAR, I've received ideas, help, and
 
1649
In addition to our work on Geo::METAR, We've received ideas, help, and
1335
1650
patches from the following folks:
1336
1651
 
1337
 
  * Otterboy <jong@watchguard.com>
 
1652
  * Ethan Dicks <ethan.dicks [at] gmail.com>
 
1653
 
 
1654
    Testing of Geo::METAR at the South Pole. Corrections and pointers
 
1655
        to interesting cases to test.
 
1656
 
 
1657
  * Otterboy <jong [at] watchguard.com>
1338
1658
 
1339
1659
    Random script fixes and initial debugging help
1340
1660
 
1341
 
  * Remi Lefebvre <remi@solaria.dhis.org>
 
1661
  * Remi Lefebvre <remi [at] solaria.dhis.org>
1342
1662
 
1343
1663
    Debian packaging as libgeo-metar-perl.deb.
1344
1664
 
1345
 
  * Mike Engelhart <mengelhart@earthtrip.com>
1346
 
 
1347
 
    Wind direction naming corrections.
1348
 
 
1349
 
  * Michael Starling <mstarling@logic.bm>
1350
 
 
1351
 
    Wind direction naming corrections.
1352
 
 
1353
 
  * Hans Einar Nielssen <hans.einar@nielssen.com>
1354
 
 
1355
 
    Wind direction naming corrections.
1356
 
 
1357
 
  * Nathan Neulinger <nneul@umr.edu>
 
1665
  * Mike Engelhart <mengelhart [at] earthtrip.com>
 
1666
 
 
1667
    Wind direction naming corrections.
 
1668
 
 
1669
  * Michael Starling <mstarling [at] logic.bm>
 
1670
 
 
1671
    Wind direction naming corrections.
 
1672
 
 
1673
  * Hans Einar Nielssen <hans.einar [at] nielssen.com>
 
1674
 
 
1675
    Wind direction naming corrections.
 
1676
 
 
1677
  * Nathan Neulinger <nneul [at] umr.edu>
1358
1678
 
1359
1679
    Lots of enhancements and corrections. Too many to list here.
1360
1680
 
 
1681
=head1 RELATED PROJECTS
 
1682
 
 
1683
B<lcdproc> at http://www.lcdproc.org/ uses Geo::METAR in lcdmetar.pl to
 
1684
display weather data on an lcd.
 
1685
 
1361
1686
=cut
1362
1687
 
1363
1688
 
1364
 
 
 
1689
# vim:expandtab:sw=4 ts=4