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