Home | Trees | Indices | Help |
---|
|
1 # 2 # A python package for interpreting METAR and SPECI weather reports. 3 # 4 # US conventions for METAR/SPECI reports are described in chapter 12 of 5 # the Federal Meteorological Handbook No.1. (FMH-1 1995), issued by NOAA. 6 # See <http://metar.noaa.gov/> 7 # 8 # International conventions for the METAR and SPECI codes are specified in 9 # the WMO Manual on Codes, vol I.1, Part A (WMO-306 I.i.A). 10 # 11 # This module handles a reports that follow the US conventions, as well 12 # the more general encodings in the WMO spec. Other regional conventions 13 # are not supported at present. 14 # 15 # The current METAR report for a given station is available at the URL 16 # http://weather.noaa.gov/pub/data/observations/metar/stations/<station>.TXT 17 # where <station> is the four-letter ICAO station code. 18 # 19 # The METAR reports for all reporting stations for any "cycle" (i.e., hour) 20 # in the last 24 hours is available in a single file at the URL 21 # http://weather.noaa.gov/pub/data/observations/metar/cycles/<cycle>Z.TXT 22 # where <cycle> is a 2-digit cycle number (e.g., "00", "05" or "23"). 23 # 24 # Copyright 2004 Tom Pollard 25 # 26 """ 27 This module defines the Metar class. A Metar object represents the weather report encoded by a single METAR code. 28 """ 29 30 31 32 __email__ = "pollard@alum.mit.edu" 33 34 __version__ = "1.2" 35 36 __LICENSE__ = """ 37 Copyright (c) 2004, 38 All rights reserved. 39 40 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 41 42 Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 43 44 Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 45 46 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 """ 48 49 import re 50 import datetime 51 import string 52 from Datatypes import * 53 54 ## Exceptions 55 59 60 ## regular expressions to decode various groups of the METAR code 61 62 MISSING_RE = re.compile(r"^[M/]+$") 63 64 TYPE_RE = re.compile(r"^(?P<type>METAR|SPECI)\s+") 65 STATION_RE = re.compile(r"^(?P<station>[A-Z][A-Z0-9]{3})\s+") 66 TIME_RE = re.compile(r"""^(?P<day>\d\d) 67 (?P<hour>\d\d) 68 (?P<min>\d\d)Z?\s+""", 69 re.VERBOSE) 70 MODIFIER_RE = re.compile(r"^(?P<mod>AUTO|FINO|NIL|TEST|CORR?|RTD|CC[A-G])\s+") 71 WIND_RE = re.compile(r"""^(?P<dir>[\dO]{3}|[0O]|///|MMM|VRB) 72 (?P<speed>P?[\dO]{2,3}|[0O]+|[/M]{2,3}) 73 (G(?P<gust>P?(\d{1,3}|[/M]{1,3})))? 74 (?P<units>KTS?|LT|K|T|KMH|MPS)? 75 (\s+(?P<varfrom>\d\d\d)V 76 (?P<varto>\d\d\d))?\s+""", 77 re.VERBOSE) 78 VISIBILITY_RE = re.compile(r"""^(?P<vis>(?P<dist>M?(\d\s*)?\d/\d\d?|M?\d+) 79 ( \s*(?P<units>SM|KM|M|U) | 80 (?P<dir>[NSEW][EW]?) )? | 81 CAVOK )\s+""", 82 re.VERBOSE) 83 RUNWAY_RE = re.compile(r"""^(RVRNO | 84 R(?P<name>\d\d(RR?|LL?|C)?)/ 85 (?P<low>(M|P)?\d\d\d\d) 86 (V(?P<high>(M|P)?\d\d\d\d))? 87 (?P<unit>FT)?[/NDU]*)\s+""", 88 re.VERBOSE) 89 WEATHER_RE = re.compile(r"""^(?P<int>(-|\+|VC)*) 90 (?P<desc>(MI|PR|BC|DR|BL|SH|TS|FZ)+)? 91 (?P<prec>(DZ|RA|SN|SG|IC|PL|GR|GS|UP|/)*) 92 (?P<obsc>BR|FG|FU|VA|DU|SA|HZ|PY)? 93 (?P<other>PO|SQ|FC|SS|DS|NSW|/+)? 94 (?P<int2>[-+])?\s+""", 95 re.VERBOSE) 96 SKY_RE= re.compile(r"""^(?P<cover>VV|CLR|SKC|SCK|NSC|NCD|BKN|SCT|FEW|[O0]VC|///) 97 (?P<height>[\dO]{2,4}|///)? 98 (?P<cloud>([A-Z][A-Z]+|///))?\s+""", 99 re.VERBOSE) 100 TEMP_RE = re.compile(r"""^(?P<temp>(M|-)?\d+|//|XX|MM)/ 101 (?P<dewpt>(M|-)?\d+|//|XX|MM)?\s+""", 102 re.VERBOSE) 103 PRESS_RE = re.compile(r"""^(?P<unit>A|Q|QNH|SLP)? 104 (?P<press>[\dO]{3,4}|////) 105 (?P<unit2>INS)?\s+""", 106 re.VERBOSE) 107 RECENT_RE = re.compile(r"""^RE(?P<desc>MI|PR|BC|DR|BL|SH|TS|FZ)? 108 (?P<prec>(DZ|RA|SN|SG|IC|PL|GR|GS|UP)*)? 109 (?P<obsc>BR|FG|FU|VA|DU|SA|HZ|PY)? 110 (?P<other>PO|SQ|FC|SS|DS)?\s+""", 111 re.VERBOSE) 112 WINDSHEAR_RE = re.compile(r"^(WS\s+)?(ALL\s+RWY|RWY(?P<name>\d\d(RR?|L?|C)?))\s+") 113 COLOR_RE = re.compile(r"""^(BLACK)?(BLU|GRN|WHT|RED)\+? 114 (/?(BLACK)?(BLU|GRN|WHT|RED)\+?)*\s*""", 115 re.VERBOSE) 116 TREND_RE = re.compile(r"^(?P<trend>TEMPO|BECMG|FCST|NOSIG)\s+") 117 118 REMARK_RE = re.compile(r"^(RMKS?|NOSPECI|NOSIG)\s+") 119 120 ## regular expressions for remark groups 121 122 AUTO_RE = re.compile(r"^AO(?P<type>\d)\s+") 123 SEALVL_PRESS_RE = re.compile(r"^SLP(?P<press>\d\d\d)\s+") 124 PEAK_WIND_RE = re.compile(r"""^P[A-Z]\s+WND\s+ 125 (?P<dir>\d\d\d) 126 (?P<speed>P?\d\d\d?)/ 127 (?P<hour>\d\d)? 128 (?P<min>\d\d)\s+""", 129 re.VERBOSE) 130 WIND_SHIFT_RE = re.compile(r"""^WSHFT\s+ 131 (?P<hour>\d\d)? 132 (?P<min>\d\d) 133 (\s+(?P<front>FROPA))?\s+""", 134 re.VERBOSE) 135 PRECIP_1HR_RE = re.compile(r"^P(?P<precip>\d\d\d\d)\s+") 136 PRECIP_24HR_RE = re.compile(r"""^(?P<type>6|7) 137 (?P<precip>\d\d\d\d)\s+""", 138 re.VERBOSE) 139 PRESS_3HR_RE = re.compile(r"""^5(?P<tend>[0-8]) 140 (?P<press>\d\d\d)\s+""", 141 re.VERBOSE) 142 TEMP_1HR_RE = re.compile(r"""^T(?P<tsign>0|1) 143 (?P<temp>\d\d\d) 144 ((?P<dsign>0|1) 145 (?P<dewpt>\d\d\d))?\s+""", 146 re.VERBOSE) 147 TEMP_6HR_RE = re.compile(r"""^(?P<type>1|2) 148 (?P<sign>0|1) 149 (?P<temp>\d\d\d)\s+""", 150 re.VERBOSE) 151 TEMP_24HR_RE = re.compile(r"""^4(?P<smaxt>0|1) 152 (?P<maxt>\d\d\d) 153 (?P<smint>0|1) 154 (?P<mint>\d\d\d)\s+""", 155 re.VERBOSE) 156 UNPARSED_RE = re.compile(r"(?P<group>\S+)\s+") 157 158 LIGHTNING_RE = re.compile(r"""^((?P<freq>OCNL|FRQ|CONS)\s+)? 159 LTG(?P<type>(IC|CC|CG|CA)*) 160 ( \s+(?P<loc>( OHD | VC | DSNT\s+ | \s+AND\s+ | 161 [NSEW][EW]? (-[NSEW][EW]?)* )+) )?\s+""", 162 re.VERBOSE) 163 164 TS_LOC_RE = re.compile(r"""TS(\s+(?P<loc>( OHD | VC | DSNT\s+ | \s+AND\s+ | 165 [NSEW][EW]? (-[NSEW][EW]?)* )+))? 166 ( \s+MOV\s+(?P<dir>[NSEW][EW]?) )?\s+""", 167 re.VERBOSE) 168 169 ## translation of weather location codes 170 171 loc_terms = [ ("OHD", "overhead"), 172 ("DSNT", "distant"), 173 ("AND", "and"), 174 ("VC", "nearby") ] 175177 """Substitute English terms for the location codes in the given string.""" 178 for code, english in loc_terms: 179 loc = loc.replace(code,english) 180 return loc181 182 ## translation of the sky-condition codes into english 183 184 SKY_COVER = { "SKC":"clear", 185 "CLR":"clear", 186 "NSC":"clear", 187 "NCD":"clear", 188 "FEW":"a few ", 189 "SCT":"scattered ", 190 "BKN":"broken ", 191 "OVC":"overcast", 192 "///":"", 193 "VV":"indefinite ceiling" } 194 195 CLOUD_TYPE = { "TCU":"towering cumulus", 196 "CU":"cumulus", 197 "CB":"cumulonimbus", 198 "SC":"stratocumulus", 199 "CBMAM":"cumulonimbus mammatus", 200 "ACC":"altocumulus castellanus", 201 "SCSL":"standing lenticular stratocumulus", 202 "CCSL":"standing lenticular cirrocumulus", 203 "ACSL":"standing lenticular altocumulus" } 204 205 ## translation of the present-weather codes into english 206 207 WEATHER_INT = { "-":"light", 208 "+":"heavy", 209 "-VC":"nearby light", 210 "+VC":"nearby heavy", 211 "VC":"nearby" } 212 WEATHER_DESC = { "MI":"shallow", 213 "PR":"partial", 214 "BC":"patches of", 215 "DR":"low drifting", 216 "BL":"blowing", 217 "SH":"showers", 218 "TS":"thunderstorm", 219 "FZ":"freezing" } 220 WEATHER_PREC = { "DZ":"drizzle", 221 "RA":"rain", 222 "SN":"snow", 223 "SG":"snow grains", 224 "IC":"ice crystals", 225 "PL":"ice pellets", 226 "GR":"hail", 227 "GS":"snow pellets", 228 "UP":"unknown precipitation", 229 "//":"" } 230 WEATHER_OBSC = { "BR":"mist", 231 "FG":"fog", 232 "FU":"smoke", 233 "VA":"volcanic ash", 234 "DU":"dust", 235 "SA":"sand", 236 "HZ":"haze", 237 "PY":"spray" } 238 WEATHER_OTHER = { "PO":"sand whirls", 239 "SQ":"squalls", 240 "FC":"funnel cloud", 241 "SS":"sandstorm", 242 "DS":"dust storm" } 243 244 WEATHER_SPECIAL = { "+FC":"tornado" } 245 246 COLOR = { "BLU":"blue", 247 "GRN":"green", 248 "WHT":"white" } 249 250 ## translation of various remark codes into English 251 252 PRESSURE_TENDENCY = { "0":"increasing, then decreasing", 253 "1":"increasing more slowly", 254 "2":"increasing", 255 "3":"increasing more quickly", 256 "4":"steady", 257 "5":"decreasing, then increasing", 258 "6":"decreasing more slowly", 259 "7":"decreasing", 260 "8":"decreasing more quickly" } 261 262 LIGHTNING_FREQUENCY = { "OCNL":"occasional", 263 "FRQ":"frequent", 264 "CONS":"constant" } 265 LIGHTNING_TYPE = { "IC":"intracloud", 266 "CC":"cloud-to-cloud", 267 "CG":"cloud-to-ground", 268 "CA":"cloud-to-air" } 269 270 REPORT_TYPE = { "METAR":"routine report", 271 "SPECI":"special report", 272 "AUTO":"automatic report", 273 "COR":"manually corrected report" } 274 275 ## Helper functions 276278 """Report success or failure of the given handler function. (DEBUG)""" 279 if match: 280 print handler.__name__," matched '"+match+"'" 281 else: 282 print handler.__name__," didn't match..."283285 """ 286 Handle otherwise unparseable main-body groups. 287 """ 288 self._unparsed_groups.append(d['group'])289 290 ## METAR report objects 291 292 debug = False 293295 """METAR (aviation meteorology report)""" 2961099298 """Parse raw METAR code.""" 299 self.code = metarcode # original METAR code 300 self.type = 'METAR' # METAR (routine) or SPECI (special) 301 self.mod = "AUTO" # AUTO (automatic) or COR (corrected) 302 self.station_id = None # 4-character ICAO station code 303 self.time = None # observation time [datetime] 304 self.cycle = None # observation cycle (0-23) [int] 305 self.wind_dir = None # wind direction [direction] 306 self.wind_speed = None # wind speed [speed] 307 self.wind_gust = None # wind gust speed [speed] 308 self.wind_dir_from = None # beginning of range for win dir [direction] 309 self.wind_dir_to = None # end of range for wind dir [direction] 310 self.vis = None # visibility [distance] 311 self.vis_dir = None # visibility direction [direction] 312 self.max_vis = None # visibility [distance] 313 self.max_vis_dir = None # visibility direction [direction] 314 self.temp = None # temperature (C) [temperature] 315 self.dewpt = None # dew point (C) [temperature] 316 self.press = None # barometric pressure [pressure] 317 self.runway = [] # runway visibility (list of tuples) 318 self.weather = [] # present weather (list of tuples) 319 self.recent = [] # recent weather (list of tuples) 320 self.sky = [] # sky conditions (list of tuples) 321 self.windshear = [] # runways w/ wind shear (list of strings) 322 self.wind_speed_peak = None # peak wind speed in last hour 323 self.wind_dir_peak = None # direction of peak wind speed in last hour 324 self.max_temp_6hr = None # max temp in last 6 hours 325 self.min_temp_6hr = None # min temp in last 6 hours 326 self.max_temp_24hr = None # max temp in last 24 hours 327 self.min_temp_24hr = None # min temp in last 24 hours 328 self.press_sea_level = None # sea-level pressure 329 self.precip_1hr = None # precipitation over the last hour 330 self.precip_3hr = None # precipitation over the last 3 hours 331 self.precip_6hr = None # precipitation over the last 6 hours 332 self.precip_24hr = None # precipitation over the last 24 hours 333 self._remarks = None # remarks (list of strings) 334 self._unparsed_groups = [] 335 self._unparsed_remarks = [] 336 337 # Assume report is for the current month, unless otherwise specified. 338 # (the year and month are implicit in METAR reports) 339 340 self._now = datetime.datetime.utcnow() 341 if utcdelta: 342 self._utcdelta = utcdelta 343 else: 344 self._utcdelta = datetime.datetime.now() - self._now 345 if month: 346 self._month = month 347 else: 348 self._month = self._now.month 349 if year: 350 self._year = year 351 else: 352 self._year = self._now.year 353 354 code = self.code+" " # (the regexps all expect trailing spaces...) 355 try: 356 ngroup = len(Metar.handlers) 357 igroup = 0 358 ifailed = -1 359 while igroup < ngroup and code: 360 pattern, handler, repeatable = Metar.handlers[igroup] 361 if debug: print handler.__name__,":",code 362 m = pattern.match(code) 363 while m: 364 ifailed = -1 365 if debug: _report_match(handler,m.group()) 366 handler(self,m.groupdict()) 367 code = code[m.end():] 368 if not repeatable: break 369 370 if debug: print handler.__name__,":",code 371 m = pattern.match(code) 372 if not m and ifailed < 0: 373 ifailed = igroup 374 igroup += 1 375 if igroup == ngroup and not m: 376 # print "** it's not a main-body group **" 377 pattern, handler = (UNPARSED_RE, _unparsedGroup) 378 if debug: print handler.__name__,":",code 379 m = pattern.match(code) 380 if debug: _report_match(handler,m.group()) 381 handler(self,m.groupdict()) 382 code = code[m.end():] 383 igroup = ifailed 384 ifailed = -2 # if it's still -2 when we run out of main-body 385 # groups, we'll try parsing this group as a remark 386 if pattern == REMARK_RE or self.press: 387 while code: 388 for pattern, handler in Metar.remark_handlers: 389 if debug: print handler.__name__,":",code 390 m = pattern.match(code) 391 if m: 392 if debug: _report_match(handler,m.group()) 393 handler(self,m.groupdict()) 394 code = pattern.sub("",code,1) 395 break 396 except Exception, err: 397 raise ParserError(handler.__name__+" failed while processing '"+code+"'\n"+string.join(err.args)) 398 raise err 399 if self._unparsed_groups: 400 code = ' '.join(self._unparsed_groups) 401 raise ParserError("Unparsed groups in body: "+code)402404 return self.string()405407 """ 408 Parse the code-type group. 409 410 The following attributes are set: 411 type [string] 412 """ 413 self.type = d['type']414416 """ 417 Parse the station id group. 418 419 The following attributes are set: 420 station_id [string] 421 """ 422 self.station_id = d['station']423425 """ 426 Parse the report-modifier group. 427 428 The following attributes are set: 429 mod [string] 430 """ 431 mod = d['mod'] 432 if mod == 'CORR': mod = 'COR' 433 if mod == 'NIL' or mod == 'FINO': mod = 'NO DATA' 434 self.mod = mod435437 """ 438 Parse the observation-time group. 439 440 The following attributes are set: 441 time [datetime] 442 cycle [int] 443 _day [int] 444 _hour [int] 445 _min [int] 446 """ 447 self._day = int(d['day']) 448 if self._day > self._now.day: 449 if self._month == 1: 450 self._month = 12 451 else: 452 self._month = self._month - 1 453 self._hour = int(d['hour']) 454 self._min = int(d['min']) 455 self.time = datetime.datetime(self._year, self._month, self._day, 456 self._hour, self._min) 457 if self._min < 45: 458 self.cycle = self._hour 459 else: 460 self.cycle = self._hour+1461463 """ 464 Parse the wind and variable-wind groups. 465 466 The following attributes are set: 467 wind_dir [direction] 468 wind_speed [speed] 469 wind_gust [speed] 470 wind_dir_from [int] 471 wind_dir_to [int] 472 """ 473 wind_dir = d['dir'].replace('O','0') 474 if wind_dir != "VRB" and wind_dir != "///" and wind_dir != "MMM": 475 self.wind_dir = direction(wind_dir) 476 wind_speed = d['speed'].replace('O','0') 477 units = d['units'] 478 if units == 'KTS' or units == 'K' or units == 'T' or units == 'LT': 479 units = 'KT' 480 if wind_speed.startswith("P"): 481 self.wind_speed = speed(wind_speed[1:], units, ">") 482 elif not MISSING_RE.match(wind_speed): 483 self.wind_speed = speed(wind_speed, units) 484 if d['gust']: 485 wind_gust = d['gust'] 486 if wind_gust.startswith("P"): 487 self.wind_gust = speed(wind_gust[1:], units, ">") 488 elif not MISSING_RE.match(wind_gust): 489 self.wind_gust = speed(wind_gust, units) 490 if d['varfrom']: 491 self.wind_dir_from = direction(d['varfrom']) 492 self.wind_dir_to = direction(d['varto'])493495 """ 496 Parse the minimum and maximum visibility groups. 497 498 The following attributes are set: 499 vis [distance] 500 vis_dir [direction] 501 max_vis [distance] 502 max_vis_dir [direction] 503 """ 504 vis = d['vis'] 505 vis_less = None 506 vis_dir = None 507 vis_units = "M" 508 vis_dist = "10000" 509 if d['dist']: 510 vis_dist = d['dist'] 511 if d['units'] and d['units'] != "U": 512 vis_units = d['units'] 513 if d['dir']: 514 vis_dir = d['dir'] 515 if vis_dist == "9999": 516 vis_dist = "10000" 517 vis_less = ">" 518 if self.vis: 519 if vis_dir: 520 self.max_vis_dir = direction(vis_dir) 521 self.max_vis = distance(vis_dist, vis_units, vis_less) 522 else: 523 if vis_dir: 524 self.vis_dir = direction(vis_dir) 525 self.vis = distance(vis_dist, vis_units, vis_less)526528 """ 529 Parse a runway visual range group. 530 531 The following attributes are set: 532 range [list of tuples] 533 . name [string] 534 . low [distance] 535 . high [distance] 536 """ 537 if d['name']: 538 name = d['name'] 539 low = distance(d['low']) 540 if d['high']: 541 high = distance(d['high']) 542 else: 543 high = low 544 self.runway.append((name,low,high))545547 """ 548 Parse a present-weather group. 549 550 The following attributes are set: 551 weather [list of tuples] 552 . intensity [string] 553 . description [string] 554 . precipitation [string] 555 . obscuration [string] 556 . other [string] 557 """ 558 inteni = d['int'] 559 if not inteni and d['int2']: 560 inteni = d['int2'] 561 desci = d['desc'] 562 preci = d['prec'] 563 obsci = d['obsc'] 564 otheri = d['other'] 565 self.weather.append((inteni,desci,preci,obsci,otheri))566568 """ 569 Parse a sky-conditions group. 570 571 The following attributes are set: 572 sky [list of tuples] 573 . cover [string] 574 . height [distance] 575 . cloud [string] 576 """ 577 height = d['height'] 578 if not height or height == "///": 579 height = None 580 else: 581 height = height.replace('O','0') 582 height = distance(int(height)*100,"FT") 583 cover = d['cover'] 584 if cover == 'SCK' or cover == 'SKC' or cover == 'CL': cover = 'CLR' 585 if cover == '0VC': cover = 'OVC' 586 cloud = d['cloud'] 587 if cloud == '///': cloud = "" 588 self.sky.append((cover,height,cloud))589591 """ 592 Parse a temperature-dewpoint group. 593 594 The following attributes are set: 595 temp temperature (Celsius) [float] 596 dewpt dew point (Celsius) [float] 597 """ 598 temp = d['temp'] 599 dewpt = d['dewpt'] 600 if temp and temp != "//" and temp != "XX" and temp != "MM" : 601 self.temp = temperature(temp) 602 if dewpt and dewpt != "//" and dewpt != "XX" and dewpt != "MM" : 603 self.dewpt = temperature(dewpt)604606 """ 607 Parse an altimeter-pressure group. 608 609 The following attributes are set: 610 press [int] 611 """ 612 press = d['press'] 613 if press != '////': 614 press = float(press.replace('O','0')) 615 if d['unit']: 616 if d['unit'] == 'A' or (d['unit2'] and d['unit2'] == 'INS'): 617 self.press = pressure(press/100,'IN') 618 elif d['unit'] == 'SLP': 619 if press < 500: 620 press = press/10 + 1000 621 else: 622 press = press/10 + 900 623 self.press = pressure(press,'MB') 624 self._remarks.append("sea-level pressure %.1fhPa" % press) 625 else: 626 self.press = pressure(press,'MB') 627 elif press > 2500: 628 self.press = pressure(press/100,'IN') 629 else: 630 self.press = pressure(press,'MB')631633 """ 634 Parse a recent-weather group. 635 636 The following attributes are set: 637 weather [list of tuples] 638 . intensity [string] 639 . description [string] 640 . precipitation [string] 641 . obscuration [string] 642 . other [string] 643 """ 644 desci = d['desc'] 645 preci = d['prec'] 646 obsci = d['obsc'] 647 otheri = d['other'] 648 self.recent.append(("",desci,preci,obsci,otheri))649651 """ 652 Parse wind-shear groups. 653 654 The following attributes are set: 655 windshear [list of strings] 656 """ 657 if d['name']: 658 self.windshear.append(d['name']) 659 else: 660 self.windshear.append("ALL")661663 """ 664 Parse (and ignore) the color groups. 665 666 The following attributes are set: 667 trend [list of strings] 668 """ 669 pass670672 """ 673 Parse (and ignore) the trend groups. 674 """ 675 # if not d['trend'] == "NOSIG": 676 # while code and not code.startswith('RMK'): 677 # try: 678 # (group, code) = code.split(None,1) 679 # except: 680 # return("",trend) 681 pass682 688690 """ 691 Parse the sea-level pressure remark group. 692 """ 693 value = float(d['press'])/10.0 694 if value < 50: 695 value += 1000 696 else: 697 value += 900 698 if not self.press: 699 self.press = pressure(value,"MB") 700 self.press_sea_level = pressure(value,"MB")701703 """ 704 Parse a 3-, 6- or 24-hour cumulative preciptation remark group. 705 """ 706 value = float(d['precip'])/100.0 707 if d['type'] == "6": 708 if self.cycle == 3 or self.cycle == 9 or self.cycle == 15 or self.cycle == 21: 709 self.precip_3hr = precipitation(value,"IN") 710 else: 711 self.precip_6hr = precipitation(value,"IN") 712 else: 713 self.precip_24hr = precipitation(value,"IN")714716 """Parse an hourly precipitation remark group.""" 717 value = float(d['precip'])/100.0 718 self.precip_1hr = precipitation(value,"IN")719721 """ 722 Parse a temperature & dewpoint remark group. 723 724 These values replace the temp and dewpt from the body of the report. 725 """ 726 value = float(d['temp'])/10.0 727 if d['tsign'] == "1": value = -value 728 self.temp = temperature(value) 729 if d['dewpt']: 730 value2 = float(d['dewpt'])/10.0 731 if d['dsign'] == "1": value2 = -value2 732 self.dewpt = temperature(value2)733735 """ 736 Parse a 6-hour maximum or minimum temperature remark group. 737 """ 738 value = float(d['temp'])/10.0 739 if d['sign'] == "1": value = -value 740 if d['type'] == "1": 741 self.max_temp_6hr = temperature(value,"C") 742 else: 743 self.min_temp_6hr = temperature(value,"C")744746 """ 747 Parse a 24-hour maximum/minimum temperature remark group. 748 """ 749 value = float(d['maxt'])/10.0 750 if d['smaxt'] == "1": value = -value 751 value2 = float(d['mint'])/10.0 752 if d['smint'] == "1": value2 = -value2 753 self.max_temp_24hr = temperature(value,"C") 754 self.min_temp_24hr = temperature(value2,"C")755757 """ 758 Parse a pressure-tendency remark group. 759 """ 760 value = float(d['press'])/10.0 761 descrip = PRESSURE_TENDENCY[d['tend']] 762 self._remarks.append("3-hr pressure change %.1fhPa, %s" % (value,descrip))763765 """ 766 Parse a peak wind remark group. 767 """ 768 peak_dir = int(d['dir']) 769 peak_speed = int(d['speed']) 770 self.wind_speed_peak = speed(peak_speed, "KT") 771 self.wind_dir_peak = direction(peak_dir) 772 peak_min = int(d['min']) 773 if d['hour']: 774 peak_hour = int(d['hour']) 775 else: 776 peak_hour = self._hour 777 self._remarks.append("peak wind %dkt from %d degrees at %d:%02d" % \ 778 (peak_speed, peak_dir, peak_hour, peak_min))779781 """ 782 Parse a wind shift remark group. 783 """ 784 if d['hour']: 785 wshft_hour = int(d['hour']) 786 else: 787 wshft_hour = self._hour 788 wshft_min = int(d['min']) 789 text = "wind shift at %d:%02d" % (wshft_hour, wshft_min) 790 if d['front']: 791 text += " (front)" 792 self._remarks.append(text)793795 """ 796 Parse a lightning observation remark group. 797 """ 798 parts = [] 799 if d['freq']: 800 parts.append(LIGHTNING_FREQUENCY[d['freq']]) 801 parts.append("lightning") 802 if d['type']: 803 ltg_types = [] 804 group = d['type'] 805 while group: 806 ltg_types.append(LIGHTNING_TYPE[group[:2]]) 807 group = group[2:] 808 parts.append("("+string.join(ltg_types,",")+")") 809 if d['loc']: 810 parts.append(xlate_loc(d['loc'])) 811 self._remarks.append(string.join(parts," "))812814 """ 815 Parse a thunderstorm location remark group. 816 """ 817 text = "thunderstorm" 818 if d['loc']: 819 text += " "+xlate_loc(d['loc']) 820 if d['dir']: 821 text += " moving %s" % d['dir'] 822 self._remarks.append(text)823825 """ 826 Parse an automatic station remark group. 827 """ 828 if d['type'] == "1": 829 self._remarks.append("Automated station") 830 elif d['type'] == "2": 831 self._remarks.append("Automated station (type 2)")832834 """ 835 Handle otherwise unparseable remark groups. 836 """ 837 self._unparsed_remarks.append(d['group'])838 839 ## the list of handler functions to use (in order) to process a METAR report 840 841 handlers = [ (TYPE_RE, _handleType, False), 842 (STATION_RE, _handleStation, False), 843 (TIME_RE, _handleTime, False), 844 (MODIFIER_RE, _handleModifier, False), 845 (WIND_RE, _handleWind, False), 846 (VISIBILITY_RE, _handleVisibility, True), 847 (RUNWAY_RE, _handleRunway, True), 848 (WEATHER_RE, _handleWeather, True), 849 (SKY_RE, _handleSky, True), 850 (TEMP_RE, _handleTemp, False), 851 (PRESS_RE, _handlePressure, False), 852 (RECENT_RE,_handleRecent, True), 853 (WINDSHEAR_RE, _handleWindShear, True), 854 (COLOR_RE, _handleColor, True), 855 (TREND_RE, _handleTrend, False), 856 (REMARK_RE, _startRemarks, False) ] 857 858 ## the list of patterns for the various remark groups, 859 ## paired with the handler functions to use to record the decoded remark. 860 861 remark_handlers = [ (AUTO_RE, _handleAutoRemark), 862 (SEALVL_PRESS_RE, _handleSealvlPressRemark), 863 (PEAK_WIND_RE, _handlePeakWindRemark), 864 (WIND_SHIFT_RE, _handleWindShiftRemark), 865 (LIGHTNING_RE, _handleLightningRemark), 866 (TS_LOC_RE, _handleTSLocRemark), 867 (TEMP_1HR_RE, _handleTemp1hrRemark), 868 (PRECIP_1HR_RE, _handlePrecip1hrRemark), 869 (PRECIP_24HR_RE, _handlePrecip24hrRemark), 870 (PRESS_3HR_RE, _handlePress3hrRemark), 871 (TEMP_6HR_RE, _handleTemp6hrRemark), 872 (TEMP_24HR_RE, _handleTemp24hrRemark), 873 (UNPARSED_RE, _unparsedRemark) ] 874 875 ## functions that return text representations of conditions for output 876878 """ 879 Return a human-readable version of the decoded report. 880 """ 881 lines = [] 882 lines.append("station: %s" % self.station_id) 883 if self.type: 884 lines.append("type: %s" % self.report_type()) 885 if self.time: 886 lines.append("time: %s" % self.time.ctime()) 887 if self.temp: 888 lines.append("temperature: %s" % self.temp.string("C")) 889 if self.dewpt: 890 lines.append("dew point: %s" % self.dewpt.string("C")) 891 if self.wind_speed: 892 lines.append("wind: %s" % self.wind()) 893 if self.wind_speed_peak: 894 lines.append("peak wind: %s" % self.peak_wind()) 895 if self.vis: 896 lines.append("visibility: %s" % self.visibility()) 897 if self.runway: 898 lines.append("visual range: %s" % self.runway_visual_range()) 899 if self.press: 900 lines.append("pressure: %s" % self.press.string("mb")) 901 if self.weather: 902 lines.append("weather: %s" % self.present_weather()) 903 if self.sky: 904 lines.append("sky: %s" % self.sky_conditions("\n ")) 905 if self.press_sea_level: 906 lines.append("sea-level pressure: %s" % self.press_sea_level.string("mb")) 907 if self.max_temp_6hr: 908 lines.append("6-hour max temp: %s" % str(self.max_temp_6hr)) 909 if self.max_temp_6hr: 910 lines.append("6-hour min temp: %s" % str(self.min_temp_6hr)) 911 if self.max_temp_24hr: 912 lines.append("24-hour max temp: %s" % str(self.max_temp_24hr)) 913 if self.max_temp_24hr: 914 lines.append("24-hour min temp: %s" % str(self.min_temp_24hr)) 915 if self.precip_1hr: 916 lines.append("1-hour precipitation: %s" % str(self.precip_1hr)) 917 if self.precip_3hr: 918 lines.append("3-hour precipitation: %s" % str(self.precip_3hr)) 919 if self.precip_6hr: 920 lines.append("6-hour precipitation: %s" % str(self.precip_6hr)) 921 if self.precip_24hr: 922 lines.append("24-hour precipitation: %s" % str(self.precip_24hr)) 923 if self._remarks: 924 lines.append("remarks:") 925 lines.append("- "+self.remarks("\n- ")) 926 if self._unparsed_remarks: 927 lines.append("- "+' '.join(self._unparsed_remarks)) 928 lines.append("METAR: "+self.code) 929 return string.join(lines,"\n")930932 """ 933 Return a textual description of the report type. 934 """ 935 if self.type == None: 936 text = "unknown report type" 937 elif REPORT_TYPE.has_key(self.type): 938 text = REPORT_TYPE[self.type] 939 else: 940 text = self.type+" report" 941 if self.cycle: 942 text += ", cycle %d" % self.cycle 943 if self.mod: 944 if REPORT_TYPE.has_key(self.mod): 945 text += " (%s)" % REPORT_TYPE[self.mod] 946 else: 947 text += " (%s)" % self.mod 948 return text949951 """ 952 Return a textual description of the wind conditions. 953 954 Units may be specified as "MPS", "KT", "KMH", or "MPH". 955 """ 956 if self.wind_speed == None: 957 return "missing" 958 elif self.wind_speed.value() == 0.0: 959 text = "calm" 960 else: 961 wind_speed = self.wind_speed.string(units) 962 if not self.wind_dir: 963 text = "variable at %s" % wind_speed 964 elif self.wind_dir_from: 965 text = "%s to %s at %s" % \ 966 (self.wind_dir_from.compass(), self.wind_dir_to.compass(), wind_speed) 967 else: 968 text = "%s at %s" % (self.wind_dir.compass(), wind_speed) 969 if self.wind_gust: 970 text += ", gusting to %s" % self.wind_gust.string(units) 971 return text972974 """ 975 Return a textual description of the peak wind conditions. 976 977 Units may be specified as "MPS", "KT", "KMH", or "MPH". 978 """ 979 if self.wind_speed_peak == None: 980 return "missing" 981 elif self.wind_speed_peak.value() == 0.0: 982 text = "calm" 983 else: 984 wind_speed = self.wind_speed_peak.string(units) 985 if not self.wind_dir_peak: 986 text = wind_speed 987 else: 988 text = "%s at %s" % (self.wind_dir_peak.compass(), wind_speed) 989 return text990992 """ 993 Return a textual description of the visibility. 994 995 Units may be statute miles ("SM") or meters ("M"). 996 """ 997 if self.vis == None: 998 return "missing" 999 if self.vis_dir: 1000 text = "%s to %s" % (self.vis.string(units), self.vis_dir.compass()) 1001 else: 1002 text = self.vis.string(units) 1003 if self.max_vis: 1004 if self.max_vis_dir: 1005 text += "; %s to %s" % (self.max_vis.string(units), self.max_vis_dir.compass()) 1006 else: 1007 text += "; %s" % string(self.max_vis) 1008 return text10091011 """ 1012 Return a textual description of the runway visual range. 1013 """ 1014 lines = [] 1015 for name,low,high in self.runway: 1016 if low != high: 1017 lines.append("on runway %s, from %d to %s" % (name, low.value(units), high.string(units))) 1018 else: 1019 lines.append("on runway %s, %s" % (name, low.string(units))) 1020 return string.join(lines,"; ")10211023 """ 1024 Return a textual description of the present weather. 1025 """ 1026 text_list = [] 1027 for weatheri in self.weather: 1028 (inteni,desci,preci,obsci,otheri) = weatheri 1029 text_parts = [] 1030 code_parts = [] 1031 if inteni: 1032 code_parts.append(inteni) 1033 text_parts.append(WEATHER_INT[inteni]) 1034 if desci: 1035 code_parts.append(desci) 1036 if desci != "SH" or not preci: 1037 text_parts.append(WEATHER_DESC[desci[0:2]]) 1038 if len(desci) == 4: 1039 text_parts.append(WEATHER_DESC[desci[2:]]) 1040 if preci: 1041 code_parts.append(preci) 1042 if len(preci) == 2: 1043 precip_text = WEATHER_PREC[preci] 1044 elif len(preci) == 4: 1045 precip_text = WEATHER_PREC[preci[:2]]+" and " 1046 precip_text += WEATHER_PREC[preci[2:]] 1047 elif len(preci) == 6: 1048 precip_text = WEATHER_PREC[preci[:2]]+", " 1049 precip_text += WEATHER_PREC[preci[2:4]]+" and " 1050 precip_text += WEATHER_PREC[preci[4:]] 1051 if desci == "TS": 1052 text_parts.append("with") 1053 text_parts.append(precip_text) 1054 if desci == "SH": 1055 text_parts.append(WEATHER_DESC[desci]) 1056 if obsci: 1057 code_parts.append(obsci) 1058 text_parts.append(WEATHER_OBSC[obsci]) 1059 1060 if otheri: 1061 code_parts.append(otheri) 1062 text_parts.append(WEATHER_OTHER[otheri]) 1063 code = string.join(code_parts) 1064 if WEATHER_SPECIAL.has_key(code): 1065 text_list.append(WEATHER_SPECIAL[code]) 1066 else: 1067 text_list.append(string.join(text_parts," ")) 1068 return string.join(text_list,"; ")10691071 """ 1072 Return a textual description of the sky conditions. 1073 """ 1074 text_list = [] 1075 for skyi in self.sky: 1076 (cover,height,cloud) = skyi 1077 if cover == "SKC" or cover == "CLR": 1078 text_list.append(SKY_COVER[cover]) 1079 else: 1080 if cloud: 1081 what = CLOUD_TYPE[cloud] 1082 elif cover != "OVC": 1083 what = "clouds" 1084 else: 1085 what = "" 1086 if cover == "VV": 1087 text_list.append("%s%s, visibility to %s" % 1088 (SKY_COVER[cover],what,str(height))) 1089 else: 1090 text_list.append("%s%s at %s" % 1091 (SKY_COVER[cover],what,str(height))) 1092 return string.join(text_list,sep)1093
Home | Trees | Indices | Help |
---|
Generated by Epydoc 3.0beta1 on Wed Jan 16 19:10:48 2008 | http://epydoc.sourceforge.net |