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