Хранилища Subversion geo-modmetar

Редакция

Редакция 13 | Редакция 15 | К новейшей редакции | Содержимое файла | Сравнить с предыдущей | Последнее изменение | Открыть журнал | RSS

Редакция Автор № строки Строка
4 alex-w 1
# AW: create fork Geo::METAR as Geo::ModMETAR
2
#
3 alex-w 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 whole
189
# hectopascals (starts with a Q) or inches of mercury (Hg) minus the decimal
190
# point (starts with an A). It should always look like ([AQ]\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::ModMETAR; # Package based on Debian Geo::METAR
224
 
225
## Required Modules
226
 
227
use 5.005;
228
use strict;
229
use vars qw($AUTOLOAD $VERSION);
230
use Carp 'cluck';
231
 
12 alex-w 232
$VERSION = '1.1'; # Based on Debian Geo::METAR 1.15
3 alex-w 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 %_weather_types_ru = (
278
    MI => 'мелкий',
279
    PI => 'частный',
280
    BC => 'местами',
7 alex-w 281
    DR => 'мелкий дождь',
3 alex-w 282
    BL => 'порывистый ветер',
7 alex-w 283
    SH => 'ливневый дождь',
3 alex-w 284
    TS => 'гроза',
285
    FZ => 'заморозки',
286
 
7 alex-w 287
    DZ => 'мелкий дождь',
3 alex-w 288
    RA => 'дождь',
289
    SN => 'снег',
290
    SG => 'снег гранулами',
291
    IC => 'ледяные кристаллы',
292
    PE => 'ледяные шарики',
293
    GR => 'град',
294
    GS => 'небольшой град',
7 alex-w 295
    UP => 'осадки',
3 alex-w 296
 
297
    BR => 'легкий туман',
298
    FG => 'туман',
299
    PRFG => 'образование тумана',
300
    FU => 'дым',
301
    VA => 'вулканический пепел',
302
    DU => 'пыль',
303
    SA => 'песок',
304
    HZ => 'дымка',
305
    PY => 'водяная пыль',
306
    PO => 'песчаные или пылевые вихри',
307
    SQ => 'шквалистый ветер',
308
    FC => 'торнадо',
309
    SS => 'песчанная буря',
310
    DS => 'пылевая буря',
311
);
312
 
313
my $_weather_types_ru_pat = join("|", keys(%_weather_types_ru));
314
 
315
my %_sky_types = (
316
    SKC => "Sky Clear",
317
    CLR => "Sky Clear",
318
    SCT => "Scattered",
319
    BKN => "Broken",
320
    FEW => "Few",
321
    OVC => "Solid Overcast",
322
    NSC => "No significant clouds",
323
    NCD => "No cloud detected",
324
);
325
 
326
my %_sky_types_ru = (
327
    SKC => "ясно",
328
    CLR => "ясно",
329
    SCT => "переменная облачность",
14 svn 330
    BKN => "облачно с прояснениями",
3 alex-w 331
    FEW => "слабая облачность",
332
    OVC => "сплошная облачность",
8 alex-w 333
    NSC => "нет существенной облачности",
3 alex-w 334
    NCD => "безоблачно",
335
);
336
 
337
my %_trend_types = (
338
    BLU => "8 km view",
339
    WHT => "5 km view",
340
    GRN => "3.7 km view",
341
    YLO => "1.6 km view",
342
    AMB => "0.8 km view",
343
    RED => "< 0.8 km view",
344
    BLACK => "airport closed",
345
    NOSIG => "No significant change",
346
    TEMPO => "Temporary change",
347
    NSW => "No significant weather",
348
    PROB => "Probability",
349
    BECMG => "Becoming",
350
    LAST => "Last",
351
);
352
 
353
my $_trend_types_pat = join("|", keys(%_trend_types));
354
 
14 svn 355
my %_trend_types_ru = (
356
    BLU => "видимость 8 км",
357
    WHT => "видимость 5 км",
358
    GRN => "видимость 3.7 км",
359
    YLO => "видимость 1.6 км",
360
    AMB => "видимость 800 м",
361
    RED => "видимость менее 800 м",
362
    BLACK => "аэропорт закрыт",
363
    NOSIG => "без существенных изменений",
364
    TEMPO => "временные изменения",
365
    NSW => "без существенной погоды",
366
    PROB => "вероятен",
367
    BECMG => "становление",
368
    LAST => "продолжается",
369
);
370
 
371
my $_trend_types_ru_pat = join("|", keys(%_trend_types_ru));
372
 
3 alex-w 373
##
374
## Constructor.
375
##
376
 
377
sub new
378
{
379
    my $this = shift;
380
    my $class = ref($this) || $this;
381
    my $self = {};
382
 
383
    ##
384
    ## UPPERCASE items have documented accssor functions (methods) or
385
    ## use AUTOLOAD, while lowercase items are reserved for internal
386
    ## use.
387
    ##
388
 
389
    $self->{VERSION}       = $VERSION;          # version number
390
    $self->{METAR}         = undef;             # the actual, raw METAR
391
    $self->{TYPE}          = undef;             # the type of report
392
    $self->{SITE}          = undef;             # site code
393
    $self->{DATE}          = undef;             # when it was issued
394
    $self->{TIME}          = undef;             # time it was issued
395
    $self->{MOD}           = undef;             # modifier (AUTO/COR)
396
    $self->{WIND_DIR_DEG}  = undef;             # wind dir in degrees
397
    $self->{WIND_DIR_ENG}  = undef;             # wind dir in english (Northwest/Southeast)
398
    $self->{WIND_DIR_RUS}  = undef;             # wind dir in russian (Северо-западный/Юго-восточный)
399
    $self->{WIND_DIR_ABB}  = undef;             # wind dir in abbreviated english (NW/SE)
400
    $self->{WIND_KTS}      = undef;             # wind speed (knots)
401
    $self->{WIND_GUST_KTS} = undef;             # wind gusts (knots)
402
    $self->{WIND_MPH}      = undef;             # wind speed (MPH)
403
    $self->{WIND_GUST_MPH} = undef;             # wind gusts (MPH)
404
    $self->{WIND_MS}       = undef;             # wind speed (m/s)
405
    $self->{WIND_GUST_MS}  = undef;             # wind gusts (m/s)
406
    $self->{WIND_VAR}      = undef;             # wind variation (text)
407
    $self->{WIND_VAR_1}    = undef;             # wind variation (direction 1)
408
    $self->{WIND_VAR_2}    = undef;             # wind variation (direction 2)
409
    $self->{WIND_VAR_ENG_1}= undef;             # wind variation (text, direction 1)
410
    $self->{WIND_VAR_ENG_2}= undef;             # wind variation (text, direction 2)
411
    $self->{VISIBILITY}    = undef;             # visibility info
412
    $self->{RUNWAY}        = [ ];               # runway vis.
413
    $self->{RH}            = undef;             # relative humidity
414
    $self->{WEATHER}       = [ ];               # current weather
415
    $self->{WEATHER_LOG}   = [ ];               # weather log
416
    $self->{SKY}           = [ ];               # current sky (cloudcover)
417
    $self->{TEMP_F}        = undef;             # current temp, celsius
418
    $self->{TEMP_C}        = undef;             # converted to fahrenheit
419
    $self->{TEMP_WC}       = undef;             # current windchill temp, celsius
420
    $self->{DEW_F}         = undef;             # dew point, celcius
421
    $self->{DEW_C}         = undef;             # dew point, fahrenheit
422
    $self->{HOURLY_TEMP_F} = undef;             # hourly current temp, celcius
423
    $self->{HOURLY_TEMP_C} = undef;             # hourly converted to fahrenheit
424
    $self->{HOURLY_DEW_F}  = undef;             # hourly dew point, celcius
425
    $self->{HOURLY_DEW_C}  = undef;             # hourly dew point, fahrenheit
426
    $self->{HOURLY_PRECIP} = undef;             # hourly precipitation
427
    $self->{ALT}           = undef;             # altimeter setting (Hg)
428
    $self->{ALT_HP}        = undef;             # altimeter setting (hPa)
9 alex-w 429
    $self->{ALT_PL}        = undef;             # pressure (Hg)
3 alex-w 430
    $self->{SLP}           = undef;             # sea level pressure
431
    $self->{REMARKS}       = undef;             # remarks
432
    $self->{WEATHER_RAW}   = [ ];               # RAW data for weather
433
    $self->{WEATHER_RUS}   = [ ];               # current weather in Russian
434
    $self->{SKY_RAW}       = [ ];               # RAW data for sky
435
    $self->{SKY_RUS}       = [ ];               # current sky in Russian
436
    $self->{VISIBILITY_RUS}= undef;             # visibility info    
14 svn 437
    $self->{SLP_RUS}       = undef;             # sea level pressure in Russian
3 alex-w 438
 
439
    $self->{tokens}        = [ ];               # the "token" list
440
    $self->{type}          = "METAR";           # the report type (METAR/SPECI)
441
                                                # default=METAR
442
    $self->{site}          = undef;             # the site code (4 chars)
443
    $self->{date_time}     = undef;             # date/time
444
    $self->{modifier}      = undef;             # the AUTO/COR modifier
445
    $self->{wind}          = undef;             # the wind information
446
    $self->{windtype}      = undef;             # the wind speed type (knots/meterpersecond/kilometersperhour)
447
    $self->{windvar}       = undef;             # the wind variation
448
    $self->{visibility}    = undef;             # visibility information
449
    $self->{runway}        = undef;             # runway visibility
450
    $self->{weather}       = [ ];               # current weather conditions
451
    $self->{sky}           = [ ];               # sky conditions (cloud cover)
452
    $self->{temp_dew}      = undef;             # temp and dew pt.
453
    $self->{alt}           = undef;             # altimeter setting
454
    $self->{pressure}      = undef;             # pressure (HPa)
455
    $self->{slp}           = undef;             # sea level pressure
456
    $self->{remarks}       = [ ];               # remarks
457
 
458
    $self->{debug}         = undef;             # enable debug trace
459
 
460
    bless $self, $class;
461
    return $self;
462
}
463
 
464
##
465
## Autoload for access methods to stuff in %fields hash. We should
466
## probably disallow access to the lower-case items as stated above,
467
## but I don't feel like being a Nazi about it. Besides, I haven't
468
## checked to see what that might break.
469
##
470
 
471
sub AUTOLOAD
472
{
473
    my $self = shift;
474
 
475
    if (not ref $self)
476
    {
477
        cluck "bad AUTOLOAD for obj [$self]";
478
    }
479
 
480
    if ($AUTOLOAD =~ /.*::(.*)/)
481
    {
482
        my $key = $1;
483
 
484
 
485
        ## Backward compatible temps...
486
 
487
        my %compat = (
488
                      F_TEMP    =>  'TEMP_F',
489
                      C_TEMP    =>  'TEMP_C',
490
                      F_DEW     =>  'DEW_F',
491
                      C_DEW     =>  'DEW_C',
492
                     );
493
 
494
        if ($compat{$key})
495
        {
496
            $key = $compat{$key};
497
        }
498
 
499
        ## Check for the items...
500
 
501
        if (exists $self->{$key})
502
        {
503
            return $self->{$key};
504
        }
505
        else
506
        {
507
            return undef;
508
        }
509
    }
510
    else
511
    {
512
        warn "strange AUTOLOAD problem!";
513
        return undef;
514
    }
515
}
516
 
517
##
518
## Get current version number.
519
##
520
 
521
sub version
522
{
523
    my $self = shift;
524
    print "version() called.\n" if $self->{debug};
525
    return $self->{VERSION};
526
}
527
 
528
##
529
## Take a METAR, tokenize, and process it.
530
##
531
 
532
sub metar
533
{
534
    my $self = shift;
535
 
536
    if (@_)
537
    {
538
        $self->{METAR} = shift;
539
        $self->{METAR} =~ s/\n//g;    ## nuke any newlines
540
        _tokenize($self);
541
        _process($self);
542
    }
543
    return $self->{METAR};
544
}
545
 
546
##
547
## Break {METAR} into parts. Stuff into @tokens.
548
##
549
 
550
sub _tokenize
551
{
552
    my $self = shift;
553
    my $tok;
554
    my @toks;
555
 
556
    # Split tokens on whitespace.
557
    @toks = split(/\s+/, $self->{METAR});
558
    $self->{tokens} = \@toks;
559
}
560
 
561
## Process @tokens to populate METAR values.
562
##
563
## This is a long and involved subroutine. It basically copies the
564
## @tokens array and treats it as a stack, popping off items,
565
## examining them, and see what they look like.  Based on their
566
## "apppearance" it takes care populating the proper fields
567
## internally.
568
 
569
sub _process
570
{
571
    my $self = shift;
572
 
573
    my @toks = @{$self->{tokens}};      # copy tokens array...
574
 
575
    my $tok;
576
 
577
    ## This is a semi-brute-force way of doing things, but the amount
578
    ## of data is relatively small, so it shouldn't be a big deal.
579
    ##
580
    ## Ideally, I'd have it skip checks for items which have been
581
    ## found, but that would make this more "linear" and I'd remove
582
    ## the pretty while loop.
583
        #
584
        # KH: modified to maintain state to not get lost in remarks and stuff
585
        # and be a lot better at parsing
586
 
587
        # states
588
 
589
        my $expect_type = 0;
590
        my $expect_site = 1;
591
        my $expect_datetime = 2;
592
        my $expect_modifier = 3;
593
        my $expect_wind = 4;
594
        my $expect_visibility = 5;
595
        my $expect_runwayvisual = 6;
596
        my $expect_presentweather = 7;
597
        my $expect_clouds = 8;
598
        my $expect_temperature = 9;
599
        my $expect_pressure = 10;
600
        my $expect_recentweather = 11;
601
        my $expect_remarks = 12;
602
        my $expect_usremarks = 13;
603
 
604
        my $parsestate = $expect_type;
605
 
606
        # windtypes
607
 
608
        my $wt_knots = 1;
609
        my $wt_mps = 2;
610
        my $wt_kph = 3;
611
 
612
    ## Assume standard report by default
613
 
614
    $self->{type} = "METAR";
615
    $self->{TYPE} = "Routine Weather Report";
616
 
617
    while (defined($tok = shift(@toks))) ## as long as there are tokens
618
    {
619
        print "trying to match [$tok] state is $parsestate\n" if $self->{debug};
620
 
621
        ##
622
        ## is it a report type?
623
        ##
624
 
625
        if (($parsestate == $expect_type) and ($tok =~ /(METAR|SPECI)/i))
626
        {
627
            $self->{type} = $tok;
628
 
629
            if ($self->{type} eq "METAR")
630
            {
631
                $self->{TYPE} = "Routine Weather Report";
632
            }
633
            elsif ($self->{type} eq "SPECI")
634
            {
635
                $self->{TYPE} = "Special Weather Report";
636
            }
637
            print "[$tok] is a report type.\n" if $self->{debug};
638
                        $parsestate = $expect_site;
639
            next;
640
        }
641
 
642
        ##
643
        ## is is a site ID?
644
        ##
645
 
646
        elsif (($parsestate <= $expect_site) and ($tok =~ /([A-Z]{4}|K[A-Z0-9]{3})/))
647
        {
648
            $self->{site} = $tok;
649
            print "[$tok] is a site ID.\n" if $self->{debug};
650
                        $parsestate = $expect_datetime;
651
            next;
652
        }
653
 
654
        ##
655
        ## is it a date/time?
656
        ##
657
 
658
        elsif (($parsestate == $expect_datetime) and ($tok =~ /\d{6,6}Z/i))
659
        {
660
            $self->{date_time} = $tok;
661
            print "[$tok] is a date/time.\n" if $self->{debug};
662
                        $parsestate = $expect_modifier;
663
            next;
664
 
665
 
666
        }
667
 
668
        ##
669
        ## is it a report modifier?
670
        ##
671
 
672
        elsif (($parsestate == $expect_modifier) and ($tok =~ /AUTO|COR|CC[A-Z]/i))
673
        {
674
            $self->{modifier} = $tok;
675
            print "[$tok] is a report modifier.\n" if $self->{debug};
676
                        $parsestate = $expect_wind;
677
            next;
678
        }
679
 
680
        ##
681
        ## is it wind information in knots?
682
        #
683
                # eew: KT seems to be optional
684
                # but making it optional fails on other stuff
685
                # sortafix: wind needs to be \d\d\d\d\d or VRB\d\d
686
                #      optional \d\d\d\d\dG\d\d\d (gust direction)
687
 
688
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_visibility) and ($tok =~ /(\d{3}|VRB)\d{2}(G\d{1,3})?(KT)?$/i))
689
        {
690
            $self->{wind} = $tok;
691
                        $self->{windtype} = $wt_knots;
692
            print "[$tok] is wind information in knots.\n" if $self->{debug};
693
                        $parsestate = $expect_wind; # stay in wind, it can have variation
694
            next;
695
        }
696
 
697
                ##
698
                ## is it wind information in meters per second?
699
                ##
700
                ## can be variable too
701
 
702
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_visibility) and ($tok =~ /^(\d{3}|VRB)\d{2}(G\d{2,3})?MPS$/))
703
        {
704
            $self->{wind} = $tok;
705
            print "[$tok] is wind information.\n" if $self->{debug};
706
                        $self->{windtype} = $wt_mps;
707
                        $parsestate = $expect_wind; # stay in wind, it can have variation
708
            next;
709
        }
710
 
711
                ##
712
                ## is it wind variation information?
713
                ##
714
 
715
                elsif (($parsestate >= $expect_wind) and ($parsestate < $expect_visibility) and ($tok =~ /^\d{3}V\d{3}$/))
716
                {
717
                        $self->{windvar} = $tok;
718
                        print "[$tok] is wind variation information.\n" if $self->{debug};
719
                        $parsestate = $expect_visibility;
720
                        next;
721
                }
722
 
723
                ##
724
                ## wind information missing at the moment?
725
                ##
726
 
727
                elsif (($parsestate >= $expect_wind) and ($parsestate < $expect_visibility) and ($tok =~ /^\/\/\/\/\/(KT|MPS)$/)){
728
                        print "[$tok] is missing wind information.\n" if $self->{debug};
729
                        $parsestate = $expect_visibility;
730
                        next;
731
                }
732
 
733
                ##
734
                ## is it visibility information in meters?
735
                ##
736
 
737
                elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /^\d{4}$/))
738
                {
739
                        $self->{visibility} = $tok;
740
            print "[$tok] is numerical visibility information.\n" if $self->{debug};
741
                        $parsestate = $expect_visibility;
742
            next;
743
        }
744
 
745
                ## auto visibility information in meters?
746
 
747
                elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /^\d{4}NDV$/))
748
                {
749
                        $self->{visibility} = $tok;
750
            print "[$tok] is automatic numerical visibility information.\n" if $self->{debug};
751
                        $parsestate = $expect_visibility;
752
            next;
753
        }
754
 
755
        ##
756
        ## is it visibility information in statute miles?
757
        ##
758
 
759
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /.*?SM$/i))
760
        {
761
            $self->{visibility} = $tok;
762
            print "[$tok] is statute miles visibility information.\n" if $self->{debug};
763
                        $parsestate = $expect_visibility;
764
            next;
765
        }
766
 
767
        ##
768
        ## is it visibility information with a leading digit?
769
                ##
770
                ## sample:
771
                ## KERV 132345Z AUTO 07008KT 1 1/4SM HZ 34/11 A3000 RMK AO2
772
                ##                           ^^^^^^^
773
        ##
774
 
775
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and  ($tok =~ /^\d$/))
776
        {
777
            $tok .= " " . shift(@toks);
778
            $self->{visibility} = $tok;
779
            print "[$tok] is multi-part visibility information.\n" if $self->{debug};
780
                        $parsestate = $expect_visibility;
781
            next;
782
        }
783
 
784
                ## visibility modifier
785
 
786
                elsif (($parsestate == $expect_visibility) and ($tok =~ /^\d{4}(N|S|E|W|NE|NW|SE|SW)$/))
787
                {
788
            print "[$tok] is a visibility modifier.\n" if $self->{debug};
789
            next;
790
                }
791
 
792
        ##
793
        ## is it runway visibility info?
794
        ##
795
                # KH: I've seen runway visibility with 'U' units
796
                # EHSB 121425Z 22010KT 1200 R27/1600U -DZ BKN003 OVC007 07/07 Q1016 AMB FCST CANCEL
797
                # U= going up, D= going down, N= no change
798
                # tendency of visual range, http://stoivane.kapsi.fi/metar/
799
 
800
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_presentweather) and ($tok =~ /R\d+(L|R|C)?\/P?\d+(VP?\d+)?(FT|D|U|N|\/)?$/i))
801
        {
802
            push (@{$self->{RUNWAY}},$tok);
803
            print "[$tok] is runway visual information.\n" if $self->{debug};
804
                        $parsestate = $expect_runwayvisual;
805
                        # there can be multiple runways, so stay at this state
806
            next;
807
        }
808
 
809
        ##
810
        ## is it current weather info?
811
        ##
812
 
813
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_clouds) and ($tok =~ /^(-|\+)?(VC)?($_weather_types_pat)+/i))
814
        {
815
            my $engl = "";
816
            my $rusl = "";
817
            my $rawl = "";
818
            my $qual = $1;
819
            my $addlqual = $2;
820
 
821
            ## qualifier
822
 
823
            if (defined $qual)
824
            {
825
                if ( $qual eq "-" ) {
826
                    $engl = "light";
827
                    $rusl = "легкий";
828
                } elsif ( $qual eq "+" ) {
829
                    $engl = "heavy";
830
                    $rusl = "сильный";
831
                } else {
832
                    $engl = ""; ## moderate
833
                    $rusl = "";
834
                }
835
                $rawl = $qual;
836
            }
837
            else
838
            {
839
                $engl = ""; ## moderate
840
                $rusl = "";
841
                $rawl = "";
842
            }
843
 
844
            while ( $tok =~ /($_weather_types_pat)/gi )
845
            {
846
                $engl .= " " . $_weather_types{$1}; ## figure out weather                
13 svn 847
                $rusl .= " " . $_weather_types_ru{$1} . ", ";
3 alex-w 848
                $rawl .= " " . $1;
849
            }
13 svn 850
            $rusl = substr($rusl, 0, length($rusl)-2);
3 alex-w 851
 
852
 
853
            ## addl qualifier
854
 
855
            if (defined $addlqual)
856
            {
857
                if ( $addlqual eq "VC" )
858
                {
859
                    $engl .= " in vicinity";
860
                    $rusl .= " в окрестностях";
861
                }
862
            }
863
 
864
            $engl =~ s/^\s//gio;
865
            $engl =~ s/\s\s/ /gio;
866
            $rusl =~ s/^\s//gio;
867
            $rusl =~ s/\s\s/ /gio;
868
            $rawl =~ s/^\s//gio;
869
            $rawl =~ s/\s\s/ /gio;
870
 
871
            push(@{$self->{WEATHER}},$engl);
872
            push(@{$self->{WEATHER_RUS}},$rusl);
873
            push(@{$self->{WEATHER_RAW}},$rawl);
874
            push(@{$self->{weather}},$tok);
875
            print "[$tok] is current weather.\n" if $self->{debug};
876
                        $parsestate = $expect_presentweather;
877
                        # there can be multiple current weather types, so stay at this state
878
            next;
879
        }
880
 
881
                ##
882
                ## special case: CAVOK
883
                ##
884
 
885
                elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok eq 'CAVOK' ))
886
                {
887
            push(@{$self->{sky}},$tok);
888
            push(@{$self->{SKY}}, "Sky Clear");
889
            push(@{$self->{SKY_RUS}}, "Ясно");
890
            push(@{$self->{SKY_RAW}},$tok);
891
            push(@{$self->{weather}},$tok);
892
            push(@{$self->{WEATHER}},"No significant weather");
893
                        $self->{visibility} = '9999';
894
                        $parsestate = $expect_temperature;
895
                        next;
896
                }
897
 
898
        ##
899
        ## is it sky conditions (clear)?
900
        ##
901
 
902
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /SKC|CLR/ ))
903
        {
904
            push(@{$self->{sky}},$tok);
905
            push(@{$self->{SKY}}, "Sky Clear");
906
            push(@{$self->{SKY_RUS}}, "Ясно");
907
            push(@{$self->{SKY_RAW}},$tok);
908
            print "[$tok] is a sky condition.\n" if $self->{debug};
909
                        $parsestate = $expect_clouds;
910
                        next;
911
        }
912
 
913
        ##
914
        ## is it sky conditions (clouds)?
915
        ##
916
                ## sky conditions can end with ///
917
 
918
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^(FEW|SCT|BKN|OVC)(\d\d\d)?(CB|TCU)?\/*$/i))
919
        {
920
            push(@{$self->{sky}},$tok);
921
            my $engl = "";
922
            my $rusl = "";
923
            my $rawl = "";
924
 
925
            $engl = $_sky_types{$1};
926
            $rusl = $_sky_types_ru{$1};
927
            $rawl = $1;
928
 
929
            if (defined $3)
930
            {
931
                if ($3 eq "TCU")
932
                {
933
                    $engl .= " Towering Cumulus";
934
                    $rusl .= ", кучевые облака";                    
935
                }
936
                elsif ($3 eq "CB")
937
                {
938
                    $engl .= " Cumulonimbus";
939
                    $rusl .= ", кучево-дождевые облака";
940
                }
941
                $rawl = $3;
942
            }
943
 
944
            if ($2 ne "")
945
            {
946
                my $agl = int($2)*100;
947
                $engl .= " at $agl" . "ft";
11 alex-w 948
                $rusl .= " на высоте " . int(($agl*0.3048)/10)*10 . " м";
3 alex-w 949
            }
950
 
951
            push(@{$self->{SKY}}, $engl);
952
            push(@{$self->{SKY_RUS}}, $rusl);
953
            push(@{$self->{SKY_RAW}}, $rawl);
954
            print "[$tok] is a sky condition.\n" if $self->{debug};
955
                        $parsestate = $expect_clouds;
956
                        # clouds DO repeat. a lot ;)
957
            next;
958
        }
959
 
960
                ##
961
                ## auto detected cloud conditions
962
                ##
963
 
964
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^(NSC|NCD)$/ )){
965
            my $engl = "";
966
            my $rusl = "";
967
 
968
            $engl = $_sky_types{$tok};
969
            $rusl = $_sky_types_ru{$tok};
970
            push(@{$self->{SKY}}, $engl);
971
            push(@{$self->{SKY_RUS}}, $rusl);
972
            push(@{$self->{SKY_RAW}}, $tok);
973
                        print "[$tok] is an automatic sky condition.\n" if $self->{debug};
974
                        $parsestate = $expect_temperature;
975
                        next;
976
                }
977
 
978
                ##
979
                ## Vertical visibility
980
                ##
981
 
982
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^VV\d+$/ )){
983
                        print "[$tok] is vertical visibility.\n" if $self->{debug};
984
                        $parsestate = $expect_temperature;
985
                        next;
986
                }
987
 
988
        ##
989
        ## is it temperature and dew point info?
990
        ##
991
 
992
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_pressure) and ($tok =~ /^(M?\d\d)\/(M?\d{0,2})/i))
993
        {
994
            next if $self->{temp_dew};
995
            $self->{temp_dew} = $tok;
996
 
997
            $self->{TEMP_C} = $1;
998
            $self->{DEW_C} = $2;
999
            $self->{TEMP_C} =~ s/^M/-/;
1000
            $self->{DEW_C} =~ s/^M/-/;
1001
 
1002
            print "[$tok] is temperature/dew point information.\n" if $self->{debug};
1003
                        $parsestate = $expect_pressure;
1004
            next;
1005
        }
1006
 
1007
        ##
1008
        ## is it an altimeter setting? (in.Hg)
1009
        ##
1010
 
1011
        elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^A(\d\d)(\d\d)$/i))
1012
        {
1013
            $self->{alt} = $tok;
1014
            $self->{ALT} = "$1.$2"+0;
1015
            $self->{ALT_HP} = "$1.$2" * 33.863886;
1016
 
1017
            print "[$tok] is an altimeter setting.\n" if $self->{debug};
1018
                        $parsestate = $expect_recentweather;
1019
            next;
1020
        }
1021
 
1022
                ##
1023
                ## is it a pressure? (hPa)
1024
                ##
1025
 
1026
                elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^Q(\d\d\d\d)$/i))
1027
                {
1028
                        $self->{pressure} = $1;
1029
            $self->{ALT_HP} = $1;
1030
                        $self->{ALT} = 0.029529983 * $self->{pressure};
1031
                        print "[$tok] is an air pressure.\n" if $self->{debug};
1032
                        $parsestate = $expect_recentweather;
1033
                        next;
1034
                }
1035
 
1036
                ##
1037
                ## recent weather?
1038
                ##
1039
 
1040
                elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^RE($_weather_types_pat)$/)){
1041
                        print "[$tok] is recent significant weather.\n" if $self->{debug};
1042
                        $parsestate = $expect_remarks;
1043
                        next;
1044
                }
1045
 
1046
                ##
1047
                ## euro type trend?
1048
                ##
1049
 
14 svn 1050
                elsif (($parsestate >= $expect_modifier) and ($tok =~ /^$_trend_types_pat/) and ($tok =~ /^$_trend_types_ru_pat/)){
3 alex-w 1051
                        print "[$tok] is a trend.\n" if $self->{debug};
1052
                        $parsestate = $expect_remarks;
1053
                        next;
1054
                }
1055
 
1056
        ##
1057
        ## us type remarks? .. can happen quite early in the process already
1058
        ##
1059
 
1060
        elsif (($parsestate >= $expect_modifier) and ($tok =~ /^RMK$/i))
1061
        {
1062
            push(@{$self->{remarks}},$tok);
1063
            print "[$tok] is a (US type) remark.\n" if $self->{debug};
1064
                        $parsestate  = $expect_usremarks;
1065
            next;
1066
        }
1067
 
1068
        ##
1069
        ## automatic station type?
1070
        ##
1071
 
1072
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^A(O\d)$/i))
1073
        {
1074
            $self->{autostationtype} = $tok;
1075
            $self->{AUTO_STATIONTYPE} = $1;
1076
            print "[$tok] is an automatic station type remark.\n" if $self->{debug};
1077
            next;
1078
        }
1079
 
1080
        ##
1081
        ## sea level pressure
1082
        ##
1083
 
1084
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^SLP(\d+)/i))
1085
        {
1086
            $self->{slp} = $tok;
1087
            $self->{SLP} = "$1 mb";
14 svn 1088
            $self->{SLP_RUS} = "$1 мбар";
3 alex-w 1089
            print "[$tok] is a sea level pressure.\n" if $self->{debug};
1090
            next;
1091
        }
1092
 
1093
        ##
1094
        ## sea level pressure not available
1095
        ##
1096
 
1097
        elsif (($parsestate == $expect_usremarks) and ($tok eq "SLPNO"))
1098
        {
1099
            $self->{slp} = "SLPNO";
1100
            $self->{SLP} = "not available";
14 svn 1101
            $self->{SLP_RUS} = "нет данных";
3 alex-w 1102
            print "[$tok] is a sea level pressure.\n" if $self->{debug};
1103
            next;
1104
        }
1105
 
1106
        ##
1107
        ## hourly precipitation
1108
        ##
1109
 
1110
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^P(\d\d\d\d)$/i))
1111
        {
1112
            $self->{hourlyprecip} = $tok;
1113
 
1114
            if ( $1 eq "0000" ) {
1115
                $self->{HOURLY_PRECIP} = "Trace";
1116
            } else {
1117
                $self->{HOURLY_PRECIP} = $1;
1118
            }
1119
        }
1120
 
1121
        ##
1122
        ## weather begin/end times
1123
        ##
1124
 
1125
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^($_weather_types_pat)([BE\d]+)$/i))
1126
        {
1127
            my $engl = "";
1128
            my $times = $2;
1129
 
1130
            $self->{weatherlog} = $tok;
1131
 
1132
            $engl = $_weather_types{$1};
1133
 
1134
            while ( $times =~ /(B|E)(\d\d)/g )
1135
            {
1136
                if ( $1 eq "B" ) {
1137
                    $engl .= " began :$2";
1138
                } else {
1139
                    $engl .= " ended :$2";
1140
                }
1141
            }
1142
 
1143
            push(@{$self->{WEATHER_LOG}}, $engl);
1144
            print "[$tok] is a weather log.\n" if $self->{debug};
1145
            next;
1146
        }
1147
 
1148
        ##
1149
        ## remarks for significant cloud types
1150
        ##
1151
 
1152
        elsif (($parsestate >= $expect_recentweather) and ($tok eq "CB" || $tok eq "TCU"))
1153
        {
1154
            push(@{$self->{sigclouds}}, $tok);
1155
 
1156
            if ( $tok eq "CB" ) {
1157
                push(@{$self->{SIGCLOUDS}}, "Cumulonimbus");
1158
            } elsif ( $tok eq "TCU" ) {
1159
                push(@{$self->{SIGCLOUDS}}, "Towering Cumulus");
1160
            }
1161
                        $parsestate = $expect_usremarks;
1162
        }
1163
 
1164
        ##
1165
        ## hourly temp/dewpoint
1166
        ##
1167
 
1168
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^T(\d)(\d\d)(\d)(\d)(\d\d)(\d)$/i))
1169
        {
1170
            $self->{hourlytempdew} = $tok;
1171
            if ( $1 == 1 ) {
1172
                $self->{HOURLY_TEMP_C} = "-";
1173
            }
1174
            $self->{HOURLY_TEMP_C} .= "$2.$3";
1175
 
1176
            $self->{HOURLY_DEW_C} = "";
1177
            if ( $4 == 1 ) {
1178
                $self->{HOURLY_DEW_C} = "-";
1179
            }
1180
            $self->{HOURLY_DEW_C} .= "$5.$6";
1181
 
1182
            print "[$tok] is a hourly temp and dewpoint.\n" if $self->{debug};
1183
            next;
1184
        }
1185
 
9 alex-w 1186
        elsif (($parsestate == $expect_usremarks) and ($tok =~ /^QFE(\d\d\d)/i))
1187
        {
1188
            $self->{ALT_PL} = $1;
1189
 
1190
            print "[$tok] is a pressure\n" if $self->{debug};
1191
            next;
1192
        }
1193
 
3 alex-w 1194
        ##
1195
        ## unknown, not in remarks yet
1196
        ##
1197
 
1198
        elsif ($parsestate < $expect_remarks)
1199
        {
1200
            push(@{$self->{unknown}},$tok);
1201
            push(@{$self->{UNKNOWN}},$tok);
1202
            print "[$tok] is unexpected at this state.\n" if $self->{debug};
1203
            next;
1204
        }
1205
 
1206
        ##
1207
        ## unknown. assume remarks
1208
        ##
1209
 
1210
        else
1211
        {
1212
            push(@{$self->{remarks}},$tok);
1213
            push(@{$self->{REMARKS}},$tok);
1214
            print "[$tok] is unknown remark.\n" if $self->{debug};
1215
            next;
1216
        }
1217
 
1218
    }
1219
 
1220
    ##
1221
    ## Now that the internal stuff is set, let's do the external
1222
    ## stuff.
1223
    ##
1224
 
1225
    $self->{SITE} = $self->{site};
1226
    $self->{DATE} = substr($self->{date_time},0,2);
1227
    $self->{TIME} = substr($self->{date_time},2,4) . " UTC";
1228
    $self->{TIME} =~ s/(\d\d)(\d\d)/$1:$2/o;
1229
    $self->{MOD}  = $self->{modifier};
1230
 
1231
    ##
1232
    ## Okay, wind finally gets interesting.
1233
    ##
1234
 
1235
    if ( defined $self->{wind} )
1236
        {
1237
        my $wind = $self->{wind};
1238
        my $dir_deg  = substr($wind,0,3);
1239
        my $wind_speed;
1240
        my $dir_eng = "";
1241
        my $dir_rus = "";
1242
                my $dir_abb = "";
1243
 
1244
        $wind_speed = $1 if($wind =~ /...(\d{2,3})/o);
1245
        # Check for wind direction
1246
        if ($dir_deg =~ /VRB/i) {
1247
            $dir_deg = $dir_eng = "Variable";
1248
            $dir_rus = "переменный";
1249
        } else {
1250
            if ($wind_speed == 0 and $dir_deg == 0) {
1251
                # Calm wind (00000KT in METAR)
1252
                $dir_eng = "Calm";
1253
                $dir_rus = "штиль";
1254
                print "wind is calm\n" if $self->{debug};
1255
            } elsif ($dir_deg < 15) {
1256
                $dir_eng = "North";
1257
                                $dir_abb = "N";
1258
                $dir_rus = "северный";
1259
            } elsif ($dir_deg < 30) {
1260
                $dir_eng = "North/Northeast";
1261
                                $dir_abb = "NNE";
1262
                $dir_rus = "северо-северо-восточный";
1263
            } elsif ($dir_deg < 60) {
1264
                $dir_eng = "Northeast";
1265
                                $dir_abb = "NE";
1266
                $dir_rus = "северо-восточный";
1267
            } elsif ($dir_deg < 75) {
1268
                $dir_eng = "East/Northeast";
1269
                                $dir_abb = "ENE";
1270
                $dir_rus = "восточный-северо-восточный";
1271
            } elsif ($dir_deg < 105) {
1272
                $dir_eng = "East";
1273
                                $dir_abb = "E";
1274
                $dir_rus = "восточный";
1275
            } elsif ($dir_deg < 120) {
1276
                $dir_eng = "East/Southeast";
1277
                                $dir_abb = "ESE";
1278
                $dir_rus = "восточный-юго-восточный";
1279
            } elsif ($dir_deg < 150) {
1280
                $dir_eng = "Southeast";
1281
                                $dir_abb = "SE";
1282
                $dir_rus = "юго-восточный";
1283
            } elsif ($dir_deg < 165) {
1284
                $dir_eng = "South/Southeast";
1285
                                $dir_abb = "SSE";
1286
                $dir_rus = "юго-юго-восточный";
1287
            } elsif ($dir_deg < 195) {
1288
                $dir_eng = "South";
1289
                                $dir_abb = "S";
1290
                $dir_rus = "южный";
1291
            } elsif ($dir_deg < 210) {
1292
                $dir_eng = "South/Southwest";
1293
                                $dir_abb = "SSW";
1294
                $dir_rus = "юго-юго-западный"
1295
            } elsif ($dir_deg < 240) {
1296
                $dir_eng = "Southwest";
1297
                                $dir_abb = "SW";
1298
                $dir_rus = "юго-западный";
1299
            } elsif ($dir_deg < 265) {
1300
                $dir_eng = "West/Southwest";
1301
                                $dir_abb = "WSW";
1302
                $dir_rus = "западно-юго-западный";
1303
            } elsif ($dir_deg < 285) {
1304
                $dir_eng = "West";
1305
                                $dir_abb = "W";
1306
                $dir_rus = "западный";
1307
            } elsif ($dir_deg < 300) {
1308
                $dir_eng = "West/Northwest";
1309
                                $dir_abb = "WNW";
1310
                $dir_rus = "западно-северо-западный";
1311
            } elsif ($dir_deg < 330) {
1312
                $dir_eng = "Northwest";
1313
                                $dir_abb = "NW";
1314
                $dir_rus = "северо-западный";
1315
            } elsif ($dir_deg < 345) {
1316
                $dir_eng = "North/Northwest";
1317
                                $dir_abb = "NNW";
1318
                $dir_rus = "северо-северо-западный";
1319
            } elsif ($dir_deg < 360) {
1320
                $dir_eng = "North";
1321
                                $dir_abb = "N";
1322
                $dir_rus = "северный";
1323
            } else {
1324
                # Shouldn't happen, but if for some reason the METAR
1325
                # information doesn't contain a reasonable direction...
1326
                $dir_eng = "undeterminable";
1327
                $dir_rus = "неопределенный";
1328
            }
1329
        }
1330
 
1331
                my $kts_speed = undef;
1332
                my $mph_speed = undef;
1333
                my $mps_speed = undef;
1334
 
1335
                my $kts_gust = "";
1336
                my $mph_gust = "";
1337
                my $mps_gust = "";
1338
 
1339
                # parse knots
1340
 
1341
                if ($self->{windtype} == $wt_knots){
1342
                        $wind =~ /...(\d\d\d?)/o;
1343
                        $kts_speed = $1;
1344
                        $mph_speed = $kts_speed * 1.15077945;
1345
                        $mps_speed = $kts_speed * 0.514444444;
1346
 
1347
                        if ($wind =~ /.{5,6}G(\d\d\d?)/o) {
1348
                                $kts_gust = $1;
1349
                                $mph_gust = $kts_gust * 1.15077945;
1350
                                $mps_gust = $kts_gust * 0.514444444;
1351
                        }
1352
                # else: parse meters/second
1353
                } elsif ($self->{windtype} == $wt_mps){
1354
                        $wind=~ /...(\d\d\d?)/o;
1355
                        $mps_speed = $1;
1356
                        $kts_speed = $mps_speed * 1.9438445; # units
1357
                        $mph_speed = $mps_speed * 2.2369363;
1358
                        if ($wind =~ /\d{5,6}G(\d\d\d?)/o) {
1359
                                $mps_gust = $1;
1360
                                $kts_gust = $mps_gust * 1.9438445;
1361
                                $mph_gust = $mps_gust * 2.2369363;
1362
                        }
1363
                } else {
8 alex-w 1364
                        warn "Geo::ModMETAR Parser error: unknown windtype\n";
3 alex-w 1365
                }
1366
 
1367
        $self->{WIND_KTS} = $kts_speed;
1368
        $self->{WIND_MPH} = $mph_speed;
1369
        $self->{WIND_MS}  = $mps_speed;
1370
 
1371
        $self->{WIND_GUST_KTS} = $kts_gust;
1372
        $self->{WIND_GUST_MPH} = $mph_gust;
1373
        $self->{WIND_GUST_MS}  = $mps_gust;
1374
 
1375
        $self->{WIND_DIR_DEG} = $dir_deg;
1376
        $self->{WIND_DIR_ENG} = $dir_eng;
1377
        $self->{WIND_DIR_ABB} = $dir_abb;
1378
        $self->{WIND_DIR_RUS} = $dir_rus;
1379
 
1380
    }
1381
 
1382
        ##
1383
        ## wind variation
1384
        ##
1385
 
1386
        if (defined $self->{windvar})
1387
        {
1388
                if ($self->{windvar} =~ /^(\d\d\d)V(\d\d\d)$/){
1389
                        $self->{WIND_VAR} = "Varying between $1 and $2";
1390
            $self->{WIND_VAR_1} = $1;
1391
            $self->{WIND_VAR_2} = $2;
1392
            my @direction = (
1393
                15 => "North",
1394
                30 => "North/Northeast",
1395
                60 => "Northeast",
1396
                75 => "East/Northeast",
1397
                105 => "East",
1398
                120 => "East/Southeast",
1399
                150 => "Southeast",
1400
                165 => "South/Southeast",
1401
                195 => "South",
1402
                210 => "South/Southwest",
1403
                240 => "Southwest",
1404
                265 => "West/Southwest",
1405
                285 => "West",
1406
                300 => "West/Northwest",
1407
                330 => "Northwest",
1408
                345 => "North/Northwest",
1409
                360 => "North",
1410
                1000 => "undeterminable");
1411
            for(my $x = 0; $x < $#direction; $x += 2) {
1412
                if($self->{WIND_VAR_1} < $direction[$x]) {
1413
                    $self->{WIND_VAR_ENG_1} = $direction[$x+1];
1414
                    last;
1415
                }
1416
            }
1417
            for(my $x = 0; $x < $#direction; $x += 2) {
1418
                if($self->{WIND_VAR_2} < $direction[$x]) {
1419
                    $self->{WIND_VAR_ENG_2} = $direction[$x+1];
1420
                    last;
1421
                }
1422
            }
1423
                }
1424
        }
1425
 
1426
    ##   
1427
    ## Calculate relative humidity
1428
    ##
1429
 
1430
    {
1431
        my $esat  = 6.11*(10**((7.5*$self->{TEMP_C})/(237.7+$self->{TEMP_C})));
1432
        my $esurf = 6.11*(10**((7.5*$self->{DEW_C})/(237.7+$self->{DEW_C})));
1433
 
1434
        $self->{RH} = 100.0 * ($esurf/$esat);
1435
    }
1436
 
1437
    ##
1438
    ## Calculate windchill temperature
1439
    ##
1440
 
1441
    {
1442
        my $windspeed = $self->{WIND_MS}*3.6;
1443
        $self->{TEMP_WC} = 13.12 + 0.6215*$self->{TEMP_C} - 11.37*($windspeed**0.16) + 0.3965*$self->{TEMP_C}*($windspeed**0.16);
1444
    }
1445
 
1446
    ##
1447
    ## Visibility.
1448
    ##
1449
 
1450
    if($self->{visibility}) {
1451
        my $vis = $self->{visibility};
1452
                # test for statute miles
1453
                if ($vis =~ /SM$/){
1454
                        $vis =~ s/SM$//oi;                              # nuke the "SM"
1455
                        if ($vis =~ /M(\d\/\d)/o) {
1456
                                $self->{VISIBILITY} = "Less than $1 statute miles";
1457
                                $self->{VISIBILITY_RUS} = "Менее чем $1 статутных миль";
1458
                        } else {
1459
                                $self->{VISIBILITY} = $vis . " statute miles";
1460
                                $self->{VISIBILITY} = $vis . " статутных миль";
1461
                        } # end if
1462
                # auto metars can have non-directional visibility reports
1463
                } elsif (($self->{MOD} eq 'AUTO') and ($vis =~ /(\d+)NDV$/)){
1464
                        $self->{VISIBILITY} = "$1 meters non-directional visibility";
1465
                        $self->{VISIBILITY_RUS} = "$1 м непрямой видимости";
1466
                } else {
1467
                        $self->{VISIBILITY} = $vis . " meters";
8 alex-w 1468
            if ($vis<1000) {
1469
                            $self->{VISIBILITY_RUS} = $vis . " м";
1470
            } else {
1471
                $vis = $vis/1000;
1472
                if (abs($vis-int($vis))>=0.5) {
1473
                    $vis = int($vis)+1;
1474
                } else {
1475
                    $vis = int($vis);
1476
                }
1477
                $self->{VISIBILITY_RUS} = $vis . " км";
1478
            }
3 alex-w 1479
                }
1480
    }
1481
 
1482
    ##
1483
    ## Calculate F temps for all C temps
1484
    ##
1485
 
1486
    foreach my $key ( keys(%$self) )
1487
    {
1488
        if ( uc($key) eq $key && $key =~ /^(.*)_C$/ )
1489
        {
1490
            my $fkey = $1 . "_F";
1491
 
1492
            next unless defined $self->{$key} && $self->{$key};
1493
 
1494
            $self->{$fkey} = sprintf("%.1f", (($self->{$key} * (9/5)) + 32));
1495
        }
1496
    }
1497
 
1498
        # join the runway group
1499
 
1500
        $self->{runway} = join(', ' , @{$self->{RUNWAY}});
1501
 
1502
}
1503
 
1504
##
1505
## Print the tokens--usually when debugging.
1506
##
1507
 
1508
sub print_tokens
1509
{
1510
    my $self = shift;
1511
    my $tok;
1512
    foreach $tok (@{$self->{tokens}}) {
1513
        print "> $tok\n";
1514
    }
1515
}
1516
 
1517
##
1518
## Turn debugging on/off.
1519
##
1520
 
1521
sub debug
1522
{
1523
    my $self = shift;
1524
    my $flag = shift;
1525
    return $self->{debug} unless defined $flag;
1526
 
1527
    if (($flag eq "Y") or ($flag eq "y") or ($flag == 1)) {
1528
        $self->{debug} = 1;
1529
    } elsif (($flag eq "N") or ($flag eq "n") or ($flag == 0)) {
1530
        $self->{debug} = 0;
1531
    }
1532
 
1533
    return $self->{debug};
1534
}
1535
 
1536
##
1537
## Dump internal data structure. Useful for debugging and such.
1538
##
1539
 
1540
sub dump
1541
{
1542
    my $self = shift;
1543
 
1544
    print "Modified METAR dump follows.\n\n";
1545
 
1546
    print "type: $self->{type}\n";
1547
    print "site: $self->{site}\n";
1548
    print "date_time: $self->{date_time}\n";
1549
    print "modifier: $self->{modifier}\n";
1550
    print "wind: $self->{wind}\n";
1551
    print "variable wind: $self->{vrbwind}\n";
1552
    print "visibility: $self->{visibility}\n";
1553
    print "runway: $self->{runway}\n";
1554
    print "weather: " . join(', ', @{$self->{weather}}) . "\n";
1555
    print "sky: " . join(', ', @{$self->{sky}}) . "\n";
1556
    print "temp_dew: $self->{temp_dew}\n";
1557
    print "alt: $self->{alt}\n";
1558
    print "pressure: $self->{pressure}\n";
1559
    print "slp: $self->{slp}\n";
1560
    print "remarks: " . join (', ', @{$self->{remarks}}) . "\n";
1561
    print "\n";
1562
 
1563
    foreach my $var ( sort(keys(%$self)) )
1564
    {
1565
        next if ( uc($var) ne $var );
1566
 
1567
        if ( ref($self->{$var}) eq "ARRAY" )
1568
        {
1569
            print "$var: ", join(", ", @{$self->{$var}}), "\n";
1570
        }
1571
        else
1572
        {
1573
            print "$var: ", $self->{$var}, "\n";
1574
        }
1575
    }
1576
}
1577
 
1578
1;
1579
 
1580
__END__
1581
 
1582
=head1 NAME
1583
 
14 svn 1584
Geo::ModMETAR - Process aviation weather reports in the METAR format.
3 alex-w 1585
 
1586
=head1 SYNOPSIS
1587
 
14 svn 1588
  use Geo::ModMETAR;
3 alex-w 1589
  use strict;
1590
 
14 svn 1591
  my $m = new Geo::ModMETAR;
3 alex-w 1592
  $m->metar("KFDY 251450Z 21012G21KT 8SM OVC065 04/M01 A3010 RMK 57014");
1593
  print $m->dump;
1594
 
1595
  exit;
1596
 
1597
=head1 DESCRIPTION
1598
 
1599
METAR reports are available on-line, thanks to the National Weather Service.
1600
Since reading the METAR format isn't easy for non-pilots, these reports are
1601
relatively useles to the common man who just wants a quick glace at the
1602
weather. This module tries to parse the METAR reports so the data can be
1603
used to create readable weather reports and/or process the data in
1604
applications.
1605
 
1606
=head1 USAGE
1607
 
1608
=head2 How you might use this
1609
 
14 svn 1610
Here is how you I<might> use the Geo::ModMETAR module.
3 alex-w 1611
 
1612
One use that I have had for this module is to query the NWS METAR page
1613
(using the LWP modules) at:
1614
 
1615
I<http://weather.noaa.gov/cgi-bin/mgetmetar.pl?cccc=EHSB>
1616
 
1617
to get an
1618
up-to-date METAR. Then, I scan thru the output, looking for what looks
1619
like a METAR string (that's not hard in Perl). Oh, EHSB can be any site
1620
location code where there is a reporting station.
1621
 
1622
I then pass the METAR into this module and get the info I want. I can
1623
then update my webcam page with the current temperature, sky conditions, or
1624
whatnot. See for yourself at http://webcam.idefix.net/
1625
 
1626
See the BUGS section for a remark about multiple passes with the same
14 svn 1627
Geo::ModMETAR object.
3 alex-w 1628
 
1629
=head2 Functions
1630
 
1631
The following functions are defined in the METAR module. Most of
1632
them are I<public>, meaning that you're supposed to use
1633
them. Some are I<private>, meaning that you're not supposed to use
1634
them -- but I won't stop you. Assume that functions are I<public>
1635
unless otherwise documented.
1636
 
1637
=over
1638
 
1639
=item metar()
1640
 
1641
metar() is the function to whwich you should pass a METAR string.  It
1642
will take care of decomposing it into its component parts converting
1643
the units and so on.
1644
 
1645
Example: C<$m-E<gt>metar("KFDY 251450Z 21012G21KT 8SM OVC065 04/M01 A3010 RMK 57014");>
1646
 
1647
=item debug()
1648
 
1649
debug() toggles debugging messages. By default, debugging is turned
1650
B<off>. Turn it on if you are developing METAR or having trouble with
1651
it.
1652
 
1653
debug() understands all of the folloing:
1654
 
1655
        Enable       Disable
1656
        ------       -------
1657
          1             0
1658
        'yes'         'no'
1659
        'on'          'off'
1660
 
1661
If you contact me for help, I'll likely ask you for some debugging
1662
output.
1663
 
1664
Example: C<$m-E<gt>debug(1);>
1665
 
1666
=item dump()
1667
 
1668
dump() will dump the internal data structure for the METAR in a
1669
semi-human readable format.
1670
 
1671
Example: C<$m-E<gt>dump;>
1672
 
1673
=item version()
1674
 
1675
version() will print out the current version.
1676
 
1677
Example: C<print $m-E<gt>version;>
1678
 
1679
=item _tokenize()
1680
 
1681
B<PRIVATE>
1682
 
1683
Called internally to break the METAR into its component tokens.
1684
 
1685
=item _process()
1686
 
1687
B<PRIVATE>
1688
 
1689
Used to make sense of the tokens found in B<_tokenize()>.
1690
 
1691
=back
1692
 
1693
=head2 Variables
1694
 
1695
After you've called B<metar()>, you'd probably like to get at
1696
the individual values for things like temperature, dew point,
1697
and so on. You do that by accessing individual variables via
1698
the METAR object.
1699
 
1700
This section lists those variables and what they represent.
1701
 
1702
If you call B<dump()>, you'll find that it spits all of these
1703
out.
1704
 
1705
=over
1706
 
1707
=item VERSION
1708
 
1709
The version of METAR.pm that you're using.
1710
 
1711
=item METAR
1712
 
1713
The actual, raw METAR.
1714
 
1715
=item TYPE
1716
 
1717
Report type in English ("Routine Weather Report" or "Special Weather Report")
1718
 
1719
=item SITE
1720
 
1721
4-letter site code.
1722
 
1723
=item DATE
1724
 
1725
The date (just the day of the month) on which the report was issued.
1726
 
1727
=item TIME
1728
 
1729
The time at which the report was issued.
1730
 
1731
=item MOD
1732
 
1733
Modifier (AUTO/COR) if any.
1734
 
1735
=item WIND_DIR_ENG
1736
 
1737
The current wind direction in English (Southwest, East, North, etc.)
1738
 
6 alex-w 1739
=item WIND_DIR_RUS
3 alex-w 1740
 
1741
The current wind direction in Russian
1742
 
1743
=item WIND_DIR_ABB
1744
 
1745
The current wind direction in abbreviated English (S, E, N, etc.)
1746
 
1747
=item WIND_DIR_DEG
1748
 
1749
The current wind direction in degrees.
1750
 
1751
=item WIND_KTS
1752
 
1753
The current wind speed in Knots.
1754
 
1755
=item WIND_MPH
1756
 
1757
The current wind speed in Miles Per Hour.
1758
 
1759
=item WIND_MS
1760
 
1761
The current wind speed in Metres Per Second.
1762
 
1763
=item WIND_GUST_KTS
1764
 
1765
The current wind gusting speed in Knots.
1766
 
1767
=item WIND_GUST_MPH
1768
 
1769
The current wind gusting speed in Miles Per Hour.
1770
 
1771
=item WIND_GUST_MS
1772
 
1773
The current wind gusting speed in Metres Per Second.
1774
 
1775
=item WIND_VAR
1776
 
1777
The wind variation in English
1778
 
1779
=item WIND_VAR_1
1780
 
1781
The first wind variation direction
1782
 
1783
=item WIND_VAR_ENG_1
1784
 
1785
The first wind variation direction in English
1786
 
1787
=item WIND_VAR_2
1788
 
1789
The second wind variation direction
1790
 
1791
=item WIND_VAR_ENG_2
1792
 
1793
The second wind variation direction in English
1794
 
1795
=item VISIBILITY
1796
 
1797
Visibility information.
1798
 
1799
=item VISIBILITY_RUS
1800
 
1801
Visibility information in Russian.
1802
 
1803
=item WIND
1804
 
1805
Wind information.
1806
 
1807
=item RUNWAY
1808
 
1809
Runway information.
1810
 
1811
=item WEATHER
1812
 
1813
Current weather (array)
1814
 
1815
==item WEATHER_RUS
1816
 
1817
Current weather in Russian (array)
1818
 
1819
==item WEATHER_RAW
1820
 
1821
Current weather in RAW-data (array)
1822
 
1823
=item WEATHER_LOG
1824
 
1825
Current weather log (array)
1826
 
1827
=item SKY
1828
 
1829
Current cloud cover (array)
1830
 
6 alex-w 1831
=item SKY_RUS
3 alex-w 1832
 
1833
Current cloud cover in Russian (array)
1834
 
6 alex-w 1835
=item SKY_RAW
3 alex-w 1836
 
1837
Current cloud cover in RAW-data (array)
1838
 
1839
=item TEMP_C
1840
 
1841
Temperature in Celsius.
1842
 
1843
=item TEMP_F
1844
 
1845
Temperature in Fahrenheit.
1846
 
1847
=item TEMP_WC
1848
 
1849
Windchill Temperature in Celsius.
1850
 
1851
=item DEW_C
1852
 
1853
Dew point in Celsius.
1854
 
1855
=item DEW_F
1856
 
1857
Dew point in Fahrenheit.
1858
 
1859
=item HOURLY_TEMP_F
1860
 
1861
Hourly current temperature, fahrenheit
1862
 
1863
=item HOURLY_TEMP_C
1864
 
1865
Hourly current temperature, celcius
1866
 
1867
=item HOURLY_DEW_F
1868
 
1869
Hourly dewpoint, fahrenheit
1870
 
1871
=item HOURLY_DEW_C
1872
 
1873
Hourly dewpoint, celcius
1874
 
1875
=item ALT
1876
 
1877
Altimeter setting (barometric pressure).
1878
 
1879
=item ALT_HP
1880
 
1881
Altimeter setting in hectopascals.
1882
 
14 svn 1883
=item ALT_PL
1884
 
1885
QFE pressure in mmHg.
1886
 
3 alex-w 1887
=item REMARKS
1888
 
1889
Any remarks in the report.
1890
 
1891
=back
1892
 
1893
=head1 NOTES
1894
 
1895
Test suite is small and incomplete. Needs work yet.
1896
 
1897
Older versions of this module were installed as "METAR" instaed of
1898
"Geo::METAR"
1899
 
1900
=head1 BUGS
1901
 
14 svn 1902
The Geo::ModMETAR is only initialized once, which means you'll get left-over
3 alex-w 1903
crud in variables when you call the metar() function twice.
1904
 
1905
What is an invalid METAR in one country is a standard one in the next.
1906
The standard is interpreted and used by meteorologists all over the world,
1907
with local variations. This means there will always be METARs that will
1908
trip the parser.
1909
 
1910
=head1 TODO
1911
 
14 svn 1912
There is a TODO file included in the Geo::ModMETAR distribution listing
3 alex-w 1913
the outstanding tasks that I or others have devised. Please check that
1914
list before you submit a bug report or request a new feture. It might
1915
already be on the TODO list.
1916
 
1917
=head1 AUTHORS AND COPYRIGHT
1918
 
1919
Copyright 1997-2000, Jeremy D. Zawodny <Jeremy [at] Zawodny.com>
1920
 
1921
Copyright 2007, Koos van den Hout <koos@kzdoos.xs4all.nl>
1922
 
1923
Copyright 2010, Alexander Wolf <alex.v.wolf@gmail.com>
1924
 
6 alex-w 1925
Geo::ModMETAR is covered under the GNU Public License (GPL) version 2 or
3 alex-w 1926
later.
1927
 
6 alex-w 1928
The Geo::ModMETAR Web site is located at:
3 alex-w 1929
 
6 alex-w 1930
  http://astro.uni-altai.ru/~aw/perl/Geo-ModMETAR/
3 alex-w 1931
 
1932
=head1 CREDITS
1933
 
1934
In addition to our work on Geo::METAR, We've received ideas, help, and
1935
patches from the following folks:
1936
 
1937
  * Ethan Dicks <ethan.dicks [at] gmail.com>
1938
 
1939
    Testing of Geo::METAR at the South Pole. Corrections and pointers
1940
        to interesting cases to test.
1941
 
1942
  * Otterboy <jong [at] watchguard.com>
1943
 
1944
    Random script fixes and initial debugging help
1945
 
1946
  * Remi Lefebvre <remi [at] solaria.dhis.org>
1947
 
1948
    Debian packaging as libgeo-metar-perl.deb.
1949
 
1950
  * Mike Engelhart <mengelhart [at] earthtrip.com>
1951
 
1952
    Wind direction naming corrections.
1953
 
1954
  * Michael Starling <mstarling [at] logic.bm>
1955
 
1956
    Wind direction naming corrections.
1957
 
1958
  * Hans Einar Nielssen <hans.einar [at] nielssen.com>
1959
 
1960
    Wind direction naming corrections.
1961
 
1962
  * Nathan Neulinger <nneul [at] umr.edu>
1963
 
1964
    Lots of enhancements and corrections. Too many to list here.
1965
 
1966
=head1 RELATED PROJECTS
1967
 
1968
B<lcdproc> at http://www.lcdproc.org/ uses Geo::METAR in lcdmetar.pl to
1969
display weather data on an lcd.
1970
 
1971
=cut
1972
 
1973
 
1974
# vim:expandtab:sw=4 ts=4