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

Редакция

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