1
# $Id: METAR.pm,v 1.8 2000/11/25 00:07:38 jzawodn Exp $
1
# $Id: METAR.pm,v 1.11 2008/01/02 13:47:00 koos Exp $
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
10
# info on the last bit which is actually a forecast: (German)
11
# http://www.wetterklima.de/flug/metar/Metarvorhersage.htm
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
16
# http://www.gids.nl/weather/eheh/metari.html
18
# 'METAR decoding in Europe'
19
# http://users.hol.gr/~chatos/VATSIM/TM/metar.html
22
# http://booty.org.uk/booty.weather/metinfo/codes/METAR_decode.htm
24
# canadian explanation
25
# http://meteocentre.com/doc/metar.html
27
# 'METAR decoding, TAF decoding'
28
# http://stoivane.kapsi.fi/metar/
3
31
# This module is used for decoding NWS METAR code.
21
50
# KLAX 251450Z 07004KT 7SM SCT100 BKN200 14/11 A3005 RMK AO2 SLP173
55
# EHSB 181325Z 24009KT 8000 -RA BR FEW011 SCT022 OVC030 07/06 Q1011 WHT WHT TEMPO GRN
24
57
# For METAR info, please see
25
58
# http://tgsv5.nws.noaa.gov/oso/oso1/oso12/metar.htm
60
# http://metar.noaa.gov/
62
# in scary detail (metar coding)
64
# http://metar.noaa.gov/table_master.jsp?sub_menu=yes&show=fmh1ch12.htm&dir=./handbook/&title=title_handbook
27
68
# The METAR specification is dictated in the Federal Meteorological Handbook
28
69
# which is available on-line at:
29
70
# http://tgsv5.nws.noaa.gov/oso/oso1/oso12/fmh1.htm
31
72
# General Structure is:
32
# SITE, DATE/TIME, WIND, VISIBILITY, CLOUDS, TEMPERATURE, PRESSURE, REMARKS
73
# TYPE, SITE, DATE/TIME, WIND, VISIBILITY, CLOUDS, TEMPERATURE, PRESSURE, REMARKS
79
# METAR: regular report
80
# SPECI: special report
82
# SITE (required, only once)
38
84
# 4-Char site identifier (KLAX for LA, KHST for Houston)
86
# DATE/TIME (required, only once)
42
88
# 6-digit time followed by "Z", indicating UTC
90
# REPORT MODIFIER (optional)
92
# AUTO = Automatic report (no human intervention)
93
# COR = Corrected METAR or SPECI
46
97
# Wind direction (\d\d\d) and speed (\d?\d\d) and optionaling gusting
47
98
# information denoted by "G" and speed (\d?\d\d) followed by "KT", for knots.
49
100
# Wind direction MAY be "VRB" (variable) instead of a compass direction.
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
51
106
# Calm wind is recorded as 00000KT.
55
# Visibility (\d+) followed by "SM" for statute miles
110
# Visibility (\d+) followed by "SM" for statute miles or no 'SM' for meters
57
113
# May be 1/(\d)SM for a fraction.
59
115
# May be M1/\d)SM for less than a given fraction. (M="-")
117
# \d\d\d\d according to KNMI
118
# lowest horizontal visibility (looking around)
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
61
# RUNWAY Visual Range Group (I've never seen this, but it's in the spec)
125
# RUNWAY Visual Range (Group)
63
127
# R(\d\d\d)(L|C|R)?/((M|P)?\d\d\d\d){1,2}FT
85
159
# The report may have a trailing CB (cumulonimbus) or TCU (towering
86
160
# cumulus) appended. ([A-Z]{2,3})?(\d\d\d)(CB|TCU)?
162
# Vertical visibility (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).
169
# hshshs - Examples of Encoding
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
176
# source http://meteocentre.com/doc/metar.html
88
178
# TEMPERATURE and DEW POINT
90
180
# (M?\d\d)/(M?\d\d) where $1 is the current temperature in degrees celcius,
121
229
use vars qw($AUTOLOAD $VERSION);
122
230
use Carp 'cluck';
130
238
my %_weather_types = (
137
TS => 'thunderstorm',
144
IC => 'ice crystals',
147
GS => 'small hail/snow pellets',
148
UP => 'unknown precip',
153
VA => 'volcanic ash',
159
PO => 'dust/sand whirls',
161
FC => 'funnel cloud(tornado/waterspout)',
245
TS => 'thunderstorm',
252
IC => 'ice crystals',
255
GS => 'small hail/snow pellets',
256
UP => 'unknown precip',
260
PRFG => 'fog banks', # officially PR is a modifier of FG
262
VA => 'volcanic ash',
268
PO => 'dust/sand whirls',
270
FC => 'funnel cloud(tornado/waterspout)',
166
275
my $_weather_types_pat = join("|", keys(%_weather_types));
168
277
my %_sky_types = (
174
OVC => "Solid Overcast",
283
OVC => "Solid Overcast",
284
NSC => "No significant clouds",
285
NCD => "No cloud detected",
291
GRN => "3.7 km view",
292
YLO => "1.6 km view",
293
AMB => "0.8 km view",
294
RED => "< 0.8 km view",
295
BLACK => "airport closed",
296
NOSIG => "No significant change",
297
TEMPO => "Temporary change",
298
NSW => "No significant weather",
299
PROB => "Probability",
304
my $_trend_types_pat = join("|", keys(%_trend_types));
198
327
$self->{TIME} = undef; # time it was issued
199
328
$self->{MOD} = undef; # modifier (AUTO/COR)
200
329
$self->{WIND_DIR_DEG} = undef; # wind dir in degrees
201
$self->{WIND_DIR_ENG} = undef; # wind dir in english (NW/SE)
330
$self->{WIND_DIR_ENG} = undef; # wind dir in english (Northwest/Southeast)
331
$self->{WIND_DIR_ABB} = undef; # wind dir in abbreviated english (NW/SE)
202
332
$self->{WIND_KTS} = undef; # wind speed (knots)
203
333
$self->{WIND_GUST_KTS} = undef; # wind gusts (knots)
204
334
$self->{WIND_MPH} = undef; # wind speed (MPH)
205
335
$self->{WIND_GUST_MPH} = undef; # wind gusts (MPH)
206
$self->{WIND_VAR_DEG} = undef; # wind variation in degrees
207
$self->{WIND_VAR_ENG} = undef; # wind variation in english
336
$self->{WIND_VAR} = undef; # wind variation (text)
337
$self->{WIND_VAR_1} = undef; # wind variation (direction 1)
338
$self->{WIND_VAR_2} = undef; # wind variation (direction 2)
208
339
$self->{VISIBILITY} = undef; # visibility info
209
$self->{RUNWAY} = undef; # runyway vis.
340
$self->{RUNWAY} = [ ]; # runway vis.
210
341
$self->{WEATHER} = [ ]; # current weather
211
342
$self->{WEATHER_LOG} = [ ]; # weather log
212
$self->{SKY} = [ ]; # curent sky
343
$self->{SKY} = [ ]; # current sky (cloudcover)
213
344
$self->{TEMP_F} = undef; # current temp, celcius
214
$self->{TEMP_C} = undef; # converted to farenheit
345
$self->{TEMP_C} = undef; # converted to fahrenheit
215
346
$self->{DEW_F} = undef; # dew point, celcius
216
$self->{DEW_C} = undef; # dew point, farenheit
347
$self->{DEW_C} = undef; # dew point, fahrenheit
217
348
$self->{HOURLY_TEMP_F} = undef; # hourly current temp, celcius
218
$self->{HOURLY_TEMP_C} = undef; # hourly converted to farenheit
349
$self->{HOURLY_TEMP_C} = undef; # hourly converted to fahrenheit
219
350
$self->{HOURLY_DEW_F} = undef; # hourly dew point, celcius
220
$self->{HOURLY_DEW_C} = undef; # hourly dew point, farenheit
351
$self->{HOURLY_DEW_C} = undef; # hourly dew point, fahrenheit
221
352
$self->{HOURLY_PRECIP} = undef; # hourly precipitation
222
$self->{ALT} = undef; # altimeter setting (Hg)
223
$self->{ALT_HP} = undef; # altimeter setting (hPa)
353
$self->{ALT} = undef; # altimeter setting
224
354
$self->{SLP} = undef; # sea level pressure
225
$self->{REMARKS} = undef; # remarks and such
355
$self->{REMARKS} = undef; # remarks
227
357
$self->{tokens} = [ ]; # the "token" list
228
358
$self->{type} = "METAR"; # the report type (METAR/SPECI)
230
360
$self->{site} = undef; # the site code (4 chars)
231
361
$self->{date_time} = undef; # date/time
232
$self->{modifier} = undef; # the AUTO/COR modifier
362
$self->{modifier} = "AUTO"; # the AUTO/COR modifier (if
233
364
$self->{wind} = undef; # the wind information
234
$self->{vrbwind} = undef; # variable wind information
365
$self->{windtype} = undef; # the wind speed type (knots/meterpersecond/kilometersperhour)
366
$self->{windvar} = undef; # the wind variation
235
367
$self->{visibility} = undef; # visibility information
236
368
$self->{runway} = undef; # runway visibility
237
369
$self->{weather} = [ ]; # current weather conditions
238
370
$self->{sky} = [ ]; # sky conditions (cloud cover)
239
371
$self->{temp_dew} = undef; # temp and dew pt.
240
$self->{alt} = undef; # altimeter setting (Hg)
241
$self->{alt_hp} = undef; # altimeter setting (hPa)
372
$self->{alt} = undef; # altimeter setting
373
$self->{pressure} = undef; # pressure (HPa)
242
374
$self->{slp} = undef; # sea level pressure
243
375
$self->{remarks} = [ ]; # remarks
368
499
## Ideally, I'd have it skip checks for items which have been
369
500
## found, but that would make this more "linear" and I'd remove
370
501
## the pretty while loop.
503
# KH: modified to maintain state to not get lost in remarks and stuff
504
# and be a lot better at parsing
510
my $expect_datetime = 2;
511
my $expect_modifier = 3;
513
my $expect_visibility = 5;
514
my $expect_runwayvisual = 6;
515
my $expect_presentweather = 7;
516
my $expect_clouds = 8;
517
my $expect_temperature = 9;
518
my $expect_pressure = 10;
519
my $expect_recentweather = 11;
520
my $expect_remarks = 12;
521
my $expect_usremarks = 13;
523
my $parsestate = $expect_type;
372
531
## Assume standard report by default
426
588
## is it a report modifier?
429
elsif (($tok =~ /AUTO/i) or ($tok =~ /COR/i))
591
elsif (($parsestate == $expect_modifier) and ($tok =~ /AUTO|COR|CC[A-Z]/i))
431
593
$self->{modifier} = $tok;
432
594
print "[$tok] is a report modifier.\n" if $self->{debug};
437
## is it wind information?
440
elsif ($tok =~ /.*?KT$/i)
595
$parsestate = $expect_wind;
600
## is it wind information in knots?
602
# eew: KT seems to be optional
603
# but making it optional fails on other stuff
604
# sortafix: wind needs to be \d\d\d\d\d or VRB\d\d
605
# optional \d\d\d\d\dG\d\d\d (gust direction)
607
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_visibility) and ($tok =~ /(\d{3}|VRB)\d{2}(G\d{1,3})?(KT)?$/i))
609
$self->{wind} = $tok;
610
$self->{windtype} = $wt_knots;
611
print "[$tok] is wind information in knots.\n" if $self->{debug};
612
$parsestate = $expect_wind; # stay in wind, it can have variation
617
## is it wind information in meters per second?
619
## can be variable too
621
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_visibility) and ($tok =~ /^(\d{3}|VRB)\d{2}(G\d{2,3})?MPS$/))
442
623
$self->{wind} = $tok;
443
624
print "[$tok] is wind information.\n" if $self->{debug};
448
## is it variable wind information?
451
elsif ($tok =~ /^\d\d\dV\d\d\d$/i)
453
$self->{vrbwind} = $tok;
454
print "[$tok] is variable wind information.\n" if $self->{debug};
459
## is it visibility information?
462
elsif ($tok =~ /.*?SM$/i)
625
$self->{windtype} = $wt_mps;
626
$parsestate = $expect_wind; # stay in wind, it can have variation
631
## is it wind variation information?
634
elsif (($parsestate >= $expect_wind) and ($parsestate < $expect_visibility) and ($tok =~ /^\d{3}V\d{3}$/))
636
$self->{windvar} = $tok;
637
print "[$tok] is wind variation information.\n" if $self->{debug};
638
$parsestate = $expect_visibility;
643
## wind information missing at the moment?
646
elsif (($parsestate >= $expect_wind) and ($parsestate < $expect_visibility) and ($tok =~ /^\/\/\/\/\/(KT|MPS)$/)){
647
print "[$tok] is missing wind information.\n" if $self->{debug};
648
$parsestate = $expect_visibility;
653
## is it visibility information in meters?
656
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /^\d{4}$/))
658
$self->{visibility} = $tok;
659
print "[$tok] is numerical visibility information.\n" if $self->{debug};
660
$parsestate = $expect_visibility;
664
## auto visibility information in meters?
666
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /^\d{4}NDV$/))
668
$self->{visibility} = $tok;
669
print "[$tok] is automatic numerical visibility information.\n" if $self->{debug};
670
$parsestate = $expect_visibility;
675
## is it visibility information in statute miles?
678
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /.*?SM$/i))
464
680
$self->{visibility} = $tok;
465
print "[$tok] is visibility information.\n" if $self->{debug};
681
print "[$tok] is statute miles visibility information.\n" if $self->{debug};
682
$parsestate = $expect_visibility;
470
## does it say CAVOK? (ceiling and visibility ok)
473
elsif ($tok =~ /^CAVOK/i)
475
$self->{visibility} = $tok;
476
print "[$tok] is visibility information, too.\n" if $self->{debug};
481
687
## is it visibility information with a leading digit?
690
## KERV 132345Z AUTO 07008KT 1 1/4SM HZ 34/11 A3000 RMK AO2
484
elsif ($tok =~ /^\d$/)
694
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_runwayvisual) and ($tok =~ /^\d$/))
486
696
$tok .= " " . shift(@toks);
487
697
$self->{visibility} = $tok;
488
print "[$tok is multi-part visibility information.\n" if $self->{debug};
698
print "[$tok] is multi-part visibility information.\n" if $self->{debug};
699
$parsestate = $expect_visibility;
703
## visibility modifier
705
elsif (($parsestate == $expect_visibility) and ($tok =~ /^\d{4}(N|S|E|W|NE|NW|SE|SW)$/))
707
print "[$tok] is a visibility modifier.\n" if $self->{debug};
493
712
## is it runway visibility info?
714
# KH: I've seen runway visibility with 'U' units
715
# EHSB 121425Z 22010KT 1200 R27/1600U -DZ BKN003 OVC007 07/07 Q1016 AMB FCST CANCEL
716
# U= going up, D= going down, N= no change
717
# tendency of visual range, http://stoivane.kapsi.fi/metar/
496
elsif ($tok =~ /R.*?FT$/i)
719
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_presentweather) and ($tok =~ /R\d+(L|R|C)?\/P?\d+(VP?\d+)?(FT|D|U|N|\/)?$/i))
498
$self->{runway} = $tok;
721
push (@{$self->{RUNWAY}},$tok);
499
722
print "[$tok] is runway visual information.\n" if $self->{debug};
723
$parsestate = $expect_runwayvisual;
724
# there can be multiple runways, so stay at this state
546
771
$engl =~ s/\s\s/ /gio;
548
773
push(@{$self->{WEATHER}},$engl);
550
774
push(@{$self->{weather}},$tok);
551
775
print "[$tok] is current weather.\n" if $self->{debug};
776
$parsestate = $expect_presentweather;
777
# there can be multiple current weather types, so stay at this state
782
## special case: CAVOK
785
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok eq 'CAVOK' ))
787
push(@{$self->{sky}},$tok);
788
push(@{$self->{SKY}}, "Sky Clear");
789
push(@{$self->{weather}},$tok);
790
push(@{$self->{WEATHER}},"No significant weather");
791
$self->{visibility} = '9999';
792
$parsestate = $expect_temperature;
556
797
## is it sky conditions (clear)?
559
elsif ( $tok eq "SKC" || $tok eq "CLR" )
800
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /SKC|CLR/ ))
561
802
push(@{$self->{sky}},$tok);
562
803
push(@{$self->{SKY}}, "Sky Clear");
804
print "[$tok] is a sky condition.\n" if $self->{debug};
805
$parsestate = $expect_clouds;
566
810
## is it sky conditions (clouds)?
812
## sky conditions can end with ///
569
elsif ( $tok =~ /^(FEW|SCT|BKN|OVC|SKC|CLR)(\d\d\d)?(CB|TCU)?$/i)
814
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^(FEW|SCT|BKN|OVC)(\d\d\d)?(CB|TCU)?\/*$/i))
571
816
push(@{$self->{sky}},$tok);
594
839
push(@{$self->{SKY}}, $engl);
595
840
print "[$tok] is a sky condition.\n" if $self->{debug};
841
$parsestate = $expect_clouds;
842
# clouds DO repeat. a lot ;)
847
## auto detected cloud conditions
850
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^(NSC|NCD)$/ )){
853
$engl = $_sky_types{$tok};
854
push(@{$self->{SKY}}, $engl);
855
print "[$tok] is an automatic sky condition.\n" if $self->{debug};
856
$parsestate = $expect_temperature;
861
## Vertical visibility
864
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_temperature) and ( $tok =~ /^VV\d+$/ )){
865
print "[$tok] is vertical visibility.\n" if $self->{debug};
866
$parsestate = $expect_temperature;
600
871
## is it temperature and dew point info?
603
elsif ($tok =~ /(M?\d\d)\/(M?\d\d)/i)
874
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_pressure) and ($tok =~ /^(M?\d\d)\/(M?\d{0,2})/i))
605
876
next if $self->{temp_dew};
606
877
$self->{temp_dew} = $tok;
611
882
$self->{DEW_C} =~ s/^M/-/;
613
884
print "[$tok] is temperature/dew point information.\n" if $self->{debug};
885
$parsestate = $expect_pressure;
618
## is it an altimeter setting? (inches in mercury)
890
## is it an altimeter setting?
621
elsif (!$in_remarks && $tok =~ /^A(\d\d)(\d\d)$/i)
893
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^A(\d\d)(\d\d)$/i))
623
895
$self->{alt} = $tok;
624
896
$self->{ALT} = "$1.$2";
898
# inches Hg pressure. How imperial can you get
899
# conversion using 'units'
901
$self->{pressure} = 33.863886 * $self->{ALT};
625
903
print "[$tok] is an altimeter setting.\n" if $self->{debug};
904
$parsestate = $expect_recentweather;
630
## is it an altimeter setting? (hectopascals)
632
elsif (!$in_remarks && $tok =~ /^Q(\d\d\d\d)$/i)
912
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^Q(\d\d\d\d)$/i))
634
$self->{alt_hp} = $tok;
635
$self->{ALT_HP} = $1;
636
print "[$tok] is an altimeter setting in hectopascals.\n"
914
$self->{pressure} = $1;
916
$self->{ALT} = 0.029529983*$self->{pressure};
917
print "[$tok] is an air pressure.\n" if $self->{debug};
918
$parsestate = $expect_recentweather;
926
elsif (($parsestate >= $expect_modifier) and ($parsestate < $expect_remarks) and ($tok =~ /^RE($_weather_types_pat)$/)){
927
print "[$tok] is recent significant weather.\n" if $self->{debug};
928
$parsestate = $expect_remarks;
936
elsif (($parsestate >= $expect_modifier) and ($tok =~ /^$_trend_types_pat/)){
937
print "[$tok] is a trend.\n" if $self->{debug};
938
$parsestate = $expect_remarks;
943
## us type remarks? .. can happen quite early in the process already
946
elsif (($parsestate >= $expect_modifier) and ($tok =~ /^RMK$/i))
948
push(@{$self->{remarks}},$tok);
949
print "[$tok] is a (US type) remark.\n" if $self->{debug};
950
$parsestate = $expect_usremarks;
642
955
## automatic station type?
645
elsif ($in_remarks && $tok =~ /^A(O\d)$/i)
958
elsif (($parsestate == $expect_usremarks) and ($tok =~ /^A(O\d)$/i))
647
960
$self->{autostationtype} = $tok;
648
961
$self->{AUTO_STATIONTYPE} = $1;
818
1122
if ($dir_deg < 15) {
819
1123
$dir_eng = "North";
820
1125
} elsif ($dir_deg < 30) {
821
1126
$dir_eng = "North/Northeast";
822
1128
} elsif ($dir_deg < 60) {
823
1129
$dir_eng = "Northeast";
824
1131
} elsif ($dir_deg < 75) {
825
1132
$dir_eng = "East/Northeast";
826
1134
} elsif ($dir_deg < 105) {
827
1135
$dir_eng = "East";
828
1137
} elsif ($dir_deg < 120) {
829
1138
$dir_eng = "East/Southeast";
830
1140
} elsif ($dir_deg < 150) {
831
1141
$dir_eng = "Southeast";
832
1143
} elsif ($dir_deg < 165) {
833
1144
$dir_eng = "South/Southeast";
834
1146
} elsif ($dir_deg < 195) {
835
1147
$dir_eng = "South";
836
1149
} elsif ($dir_deg < 210) {
837
1150
$dir_eng = "South/Southwest";
838
1152
} elsif ($dir_deg < 240) {
839
1153
$dir_eng = "Southwest";
840
1155
} elsif ($dir_deg < 265) {
841
1156
$dir_eng = "West/Southwest";
842
1158
} elsif ($dir_deg < 285) {
843
1159
$dir_eng = "West";
844
1161
} elsif ($dir_deg < 300) {
845
1162
$dir_eng = "West/Northwest";
846
1164
} elsif ($dir_deg < 330) {
847
1165
$dir_eng = "Northwest";
848
1167
} elsif ($dir_deg < 345) {
849
1168
$dir_eng = "North/Northwest";
851
1171
$dir_eng = "North";
855
$wind =~ /...(\d\d\d?)/o;
857
my $mph_speed = $kts_speed * 1.1508;
861
if ($wind =~ /.{5,6}G(\d\d\d?)/o) {
863
$mph_gust = $kts_gust * 1.1508;
1176
my $kts_speed = undef;
1177
my $mph_speed = undef;
1184
if ($self->{windtype} == $wt_knots){
1185
$wind =~ /...(\d\d\d?)/o;
1187
$mph_speed = $kts_speed * 1.1508;
1190
if ($wind =~ /.{5,6}G(\d\d\d?)/o) {
1192
$mph_gust = $kts_gust * 1.1508;
1194
# else: parse meters/second
1195
} elsif ($self->{windtype} == $wt_mps){
1196
$wind=~ /...(\d\d\d?)/o;
1198
$kts_speed = $mps_speed * 1.9438445; # units
1199
$mph_speed = $mps_speed * 2.2369363;
1200
if ($wind =~ /\d{5,6}G(\d\d\d?)/o) {
1202
$kts_gust = $mps_gust * 1.9438445;
1203
$mph_gust = $mps_gust * 2.2369363;
1206
warn "Geo::METAR Parser error: unknown windtype\n";
866
1209
$self->{WIND_KTS} = $kts_speed;
867
1210
$self->{WIND_MPH} = $mph_speed;
872
1215
$self->{WIND_DIR_DEG} = $dir_deg;
873
1216
$self->{WIND_DIR_ENG} = $dir_eng;
1217
$self->{WIND_DIR_ABB} = $dir_abb;
878
## Variable wind information
1225
if (defined $self->{windvar})
882
if ($self->{vrbwind}) {
883
my $vrbwind = $self->{vrbwind};
884
my $vrbwind_deg_1 = substr($vrbwind,0,3);
885
my $vrbwind_deg_2 = substr($vrbwind,4,3);
886
my $vrbwind_eng_1 = "";
887
my $vrbwind_eng_2 = "";
889
if ($vrbwind_deg_1 < 15) { $vrbwind_eng_1 = "North"; }
890
elsif ($vrbwind_deg_1 < 30) { $vrbwind_eng_1 = "North/Northeast"; }
891
elsif ($vrbwind_deg_1 < 60) { $vrbwind_eng_1 = "Northeast"; }
892
elsif ($vrbwind_deg_1 < 75) { $vrbwind_eng_1 = "East/Northeast"; }
893
elsif ($vrbwind_deg_1 < 105) { $vrbwind_eng_1 = "East"; }
894
elsif ($vrbwind_deg_1 < 120) { $vrbwind_eng_1 = "East/Southeast"; }
895
elsif ($vrbwind_deg_1 < 150) { $vrbwind_eng_1 = "Southeast"; }
896
elsif ($vrbwind_deg_1 < 165) { $vrbwind_eng_1 = "South/Southeast"; }
897
elsif ($vrbwind_deg_1 < 195) { $vrbwind_eng_1 = "South"; }
898
elsif ($vrbwind_deg_1 < 210) { $vrbwind_eng_1 = "South/Southwest"; }
899
elsif ($vrbwind_deg_1 < 240) { $vrbwind_eng_1 = "Southwest"; }
900
elsif ($vrbwind_deg_1 < 265) { $vrbwind_eng_1 = "West/Southwest"; }
901
elsif ($vrbwind_deg_1 < 285) { $vrbwind_eng_1 = "West"; }
902
elsif ($vrbwind_deg_1 < 300) { $vrbwind_eng_1 = "West/Northwest"; }
903
elsif ($vrbwind_deg_1 < 330) { $vrbwind_eng_1 = "Northwest"; }
904
elsif ($vrbwind_deg_1 < 345) { $vrbwind_eng_1 = "North/Northwest"; }
905
else { $vrbwind_eng_1 = "North"; }
907
if ($vrbwind_deg_2 < 15) { $vrbwind_eng_2 = "North"; }
908
elsif ($vrbwind_deg_2 < 30) { $vrbwind_eng_2 = "North/Northeast"; }
909
elsif ($vrbwind_deg_2 < 60) { $vrbwind_eng_2 = "Northeast"; }
910
elsif ($vrbwind_deg_2 < 75) { $vrbwind_eng_2 = "East/Northeast"; }
911
elsif ($vrbwind_deg_2 < 105) { $vrbwind_eng_2 = "East"; }
912
elsif ($vrbwind_deg_2 < 120) { $vrbwind_eng_2 = "East/Southeast"; }
913
elsif ($vrbwind_deg_2 < 150) { $vrbwind_eng_2 = "Southeast"; }
914
elsif ($vrbwind_deg_2 < 165) { $vrbwind_eng_2 = "South/Southeast"; }
915
elsif ($vrbwind_deg_2 < 195) { $vrbwind_eng_2 = "South"; }
916
elsif ($vrbwind_deg_2 < 210) { $vrbwind_eng_2 = "South/Southwest"; }
917
elsif ($vrbwind_deg_2 < 240) { $vrbwind_eng_2 = "Southwest"; }
918
elsif ($vrbwind_deg_2 < 265) { $vrbwind_eng_2 = "West/Southwest"; }
919
elsif ($vrbwind_deg_2 < 285) { $vrbwind_eng_2 = "West"; }
920
elsif ($vrbwind_deg_2 < 300) { $vrbwind_eng_2 = "West/Northwest"; }
921
elsif ($vrbwind_deg_2 < 330) { $vrbwind_eng_2 = "Northwest"; }
922
elsif ($vrbwind_deg_2 < 345) { $vrbwind_eng_2 = "North/Northwest"; }
923
else { $vrbwind_eng_2 = "North"; }
925
push @{$self->{WIND_VAR_DEG}}, $vrbwind_deg_1;
926
push @{$self->{WIND_VAR_DEG}}, $vrbwind_deg_2;
927
push @{$self->{WIND_VAR_ENG}}, $vrbwind_eng_1;
928
push @{$self->{WIND_VAR_ENG}}, $vrbwind_eng_2;
1227
if ($self->{windvar} =~ /^(\d\d\d)V(\d\d\d)$/){
1228
$self->{WIND_VAR} = "Varying between $1 and $2";
1229
$self->{WIND_VAR_1} = $1;
1230
$self->{WIND_VAR_2} = $2;
937
if ($self->{visibility}) {
938
my $vis = $self->{visibility};
939
$vis =~ s/SM$//oi; # nuke the "SM"
940
if ($vis =~ /^CAVOK$/i) {
941
$self->{VISIBILITY} = "Ceiling and visibility OK";
943
elsif ($vis =~ /M(\d\/\d)/o) {
944
$self->{VISIBILITY} = "Less than $1 statute miles";
946
$self->{VISIBILITY} = $vis . " Statute Miles";
1239
my $vis = $self->{visibility};
1240
# test for statute miles
1242
$vis =~ s/SM$//oi; # nuke the "SM"
1243
if ($vis =~ /M(\d\/\d)/o) {
1244
$self->{VISIBILITY} = "Less than $1 statute miles";
1246
$self->{VISIBILITY} = $vis . " Statute Miles";
1248
# auto metars can have non-directional visibility reports
1249
} elsif (($self->{MOD} eq 'AUTO') and ($vis =~ /(\d+)NDV$/)){
1250
$self->{VISIBILITY} = "$1 meters non-directional visibility";
1252
$self->{VISIBILITY} = $vis . " meters";
952
## Convert ALT to ALT_HP or vice versa
956
if ($self->{ALT} && !$self->{ALT_HP}) {
957
my $alt = $self->{ALT};
958
$alt = $alt * 1.33; # mmHg to hPa
959
$self->{ALT_HP} = $alt;
961
elsif (!$self->{ALT} && $self->{ALT_HP}) {
962
my $alt = $self->{ALT_HP};
963
$alt = $alt * 0.75; # hPa to mmHg
969
1257
## Calculate F temps for all C temps
1023
1316
my $self = shift;
1025
print "METAR dump follows.\n\n";
1318
print "Modified METAR dump follows.\n\n";
1027
1320
print "type: $self->{type}\n";
1028
1321
print "site: $self->{site}\n";
1029
1322
print "date_time: $self->{date_time}\n";
1030
1323
print "modifier: $self->{modifier}\n";
1031
1324
print "wind: $self->{wind}\n";
1032
print "variable wind: $self->{vrbwind}\n";
1033
1325
print "visibility: $self->{visibility}\n";
1034
1326
print "runway: $self->{runway}\n";
1035
1327
print "weather: " . join(', ', @{$self->{weather}}) . "\n";
1036
1328
print "sky: " . join(', ', @{$self->{sky}}) . "\n";
1037
1329
print "temp_dew: $self->{temp_dew}\n";
1038
print "alt: $self->{alt}\n";
1039
print "alt_hp: $self->{alt_hp}\n";
1330
print "alt: $self->{ALT}\n";
1040
1331
print "slp: $self->{slp}\n";
1041
1332
print "remarks: " . join (', ', @{$self->{remarks}}) . "\n";
1089
1382
Here is how you I<might> use the Geo::METAR module.
1091
1384
One use that I have had for this module is to query the NWS METAR page
1092
(using the LWP modules) at:
1094
I<http://weather.noaa.gov/cgi-bin/mgetmetar.pl?cccc=KFDY>
1096
to get an up-to-date METAR. Then, I scan thru the output, looking for what
1097
looks like a METAR string (that's not hard in Perl). Oh, KFDY can be any site
1385
(using the LWP modules) at
1386
http://weather.noaa.gov/cgi-bin/mgetmetar.pl?cccc=EHSB to get an
1387
up-to-date METAR. Then, I scan thru the output, looking for what looks
1388
like a METAR string (that's not hard in Perl). Oh, EHSB can be any site
1098
1389
location code where there is a reporting station.
1100
1391
I then pass the METAR into this module and get the info I want. I can
1101
then update my home page with the current temperature, sky conditions, or
1392
then update my webcam page with the current temperature, sky conditions, or
1393
whatnot. See for yourself at http://webcam.idefix.net/
1395
See the BUGS section for a remark about multiple passes with the same
1104
1398
=head2 Functions
1106
The following functions are defined in the AcctInfo module. Most of
1400
The following functions are defined in the METAR module. Most of
1107
1401
them are I<public>, meaning that you're supposed to use
1108
1402
them. Some are I<private>, meaning that you're not supposed to use
1109
1403
them -- but I won't stop you. Assume that functions are I<public>
1296
1614
Older versions of this module were installed as "METAR" instaed of
1299
=head2 Adding a find() method.
1301
I should add a function called find() which can be passed a big chunk
1302
of text (or a ref to one) and a site identifier. It will scan through
1303
the text and find the METAR. The result can be fed back into this
1304
module for processing.
1306
That'd be cool, I think.
1310
There currently aren't any known BUGS (features which don't work as
1311
advetised). There are lacking features. See the TODO section for more
1619
The Geo::METAR is only initialized once, which means you'll get left-over
1620
crud in variables when you call the metar() function twice.
1622
What is an invalid METAR in one country is a standard one in the next.
1623
The standard is interpreted and used by meteorologists all over the world,
1624
with local variations. This means there will always be METARs that will
1318
1631
list before you submit a bug report or request a new feture. It might
1319
1632
already be on the TODO list.
1321
=head1 AUTHOR AND COPYRIGHT
1323
Copyright 1997-2000, Jeremy D. Zawodny <Jeremy@Zawodny.com>
1634
=head1 AUTHORS AND COPYRIGHT
1636
Copyright 1997-2000, Jeremy D. Zawodny <Jeremy [at] Zawodny.com>
1638
Copyright 2007, Koos van den Hout <koos@kzdoos.xs4all.nl>
1325
1640
Geo::METAR is covered under the GNU Public License (GPL) version 2 or
1328
1643
The Geo::METAR Web site is located at:
1330
http://www.wcnet.org/~jzawodn/perl/Geo-METAR/
1645
http://idefix.net/~koos/perl/Geo-METAR/
1334
In addition to my work on Geo::METAR, I've received ideas, help, and
1649
In addition to our work on Geo::METAR, We've received ideas, help, and
1335
1650
patches from the following folks:
1337
* Otterboy <jong@watchguard.com>
1652
* Ethan Dicks <ethan.dicks [at] gmail.com>
1654
Testing of Geo::METAR at the South Pole. Corrections and pointers
1655
to interesting cases to test.
1657
* Otterboy <jong [at] watchguard.com>
1339
1659
Random script fixes and initial debugging help
1341
* Remi Lefebvre <remi@solaria.dhis.org>
1661
* Remi Lefebvre <remi [at] solaria.dhis.org>
1343
1663
Debian packaging as libgeo-metar-perl.deb.
1345
* Mike Engelhart <mengelhart@earthtrip.com>
1347
Wind direction naming corrections.
1349
* Michael Starling <mstarling@logic.bm>
1351
Wind direction naming corrections.
1353
* Hans Einar Nielssen <hans.einar@nielssen.com>
1355
Wind direction naming corrections.
1357
* Nathan Neulinger <nneul@umr.edu>
1665
* Mike Engelhart <mengelhart [at] earthtrip.com>
1667
Wind direction naming corrections.
1669
* Michael Starling <mstarling [at] logic.bm>
1671
Wind direction naming corrections.
1673
* Hans Einar Nielssen <hans.einar [at] nielssen.com>
1675
Wind direction naming corrections.
1677
* Nathan Neulinger <nneul [at] umr.edu>
1359
1679
Lots of enhancements and corrections. Too many to list here.
1681
=head1 RELATED PROJECTS
1683
B<lcdproc> at http://www.lcdproc.org/ uses Geo::METAR in lcdmetar.pl to
1684
display weather data on an lcd.
1689
# vim:expandtab:sw=4 ts=4