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