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

Редакция

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