## # This software was developed and / or modified by Raytheon Company, # pursuant to Contract DG133W-05-CQ-1067 with the US Government. # # U.S. EXPORT CONTROLLED TECHNICAL DATA # This software product contains export-restricted data whose # export/transfer/disclosure is restricted by U.S. law. Dissemination # to non-U.S. persons whether in the United States or abroad requires # an export license or other authorization. # # Contractor Name: Raytheon Company # Contractor Address: 6825 Pine Street, Suite 340 # Mail Stop B8 # Omaha, NE 68106 # 402.291.0100 # # See the AWIPS II Master Rights File ("Master Rights File.pdf") for # further licensing information. ## # # Name: # AvnLib.py # GFS1-NHD:A6626.0000-SCRIPT;1.45 # # Status: # DELIVERED # # History: # Revision 1.45 (DELIVERED) # Created: 05-NOV-2009 20:42:46 OBERFIEL # makeTempo() function 'promotes' all items to # prevailing conditions in dictionary. # # Revision 1.44 (DELIVERED) # Created: 25-SEP-2009 14:30:06 OBERFIEL # Change to support hacked code. # # Revision 1.43 (DELIVERED) # Created: 24-AUG-2009 14:35:19 OBERFIEL # Added code to support "CB not in TAF" when evaluating CCFP # guidance. # # Revision 1.42 (DELIVERED) # Created: 20-JUL-2009 10:02:28 GILMOREDM # Fixed error that gave wrong flight cat when vsby or cig was # missing # # Revision 1.41 (DELIVERED) # Created: 22-APR-2009 19:30:15 OBERFIEL # Added exception handling when BUFR model data is being # processed. # # Revision 1.40 (REVIEW) # Created: 20-MAR-2009 18:22:14 OBERFIEL # Removed code cruft. ETA changed to NAM. NGMMOS removed. # # Revision 1.39 (UNDER WORK) # Created: 17-MAR-2009 13:33:51 GILMOREDM # Code now properly checks TEMPO wx # # Revision 1.38 (DELIVERED) # Created: 31-DEC-2008 10:14:27 OBERFIEL # Changes to support amending TAFs prior to valid period. # # Revision 1.37 (DELIVERED) # Created: 31-OCT-2008 10:35:15 GILMOREDM # fixed issue in flightCategory # # Revision 1.36 (DELIVERED) # Created: 18-SEP-2008 13:34:09 OBERFIEL # Expanded grace period of the transmission window slightly # # Revision 1.35 (DELIVERED) # Created: 17-SEP-2008 20:33:32 OBERFIEL # Lengthened the grace period for regularly issued TAF a tiny # bit. # # Revision 1.34 (DELIVERED) # Created: 02-SEP-2008 13:08:50 OBERFIEL # Updated rule to account for 30-h length TAF and allow COR # on the first line # # Revision 1.33 (DELIVERED) # Created: 01-AUG-2008 15:44:46 OBERFIEL # Synch'd up with changes in OB8.3.1 # # Revision 1.32 (DELIVERED) # Created: 19-JUN-2008 14:18:09 OBERFIEL # Fixed getFmtValidTime to allow for 30-h header. # # Revision 1.31 (INITIALIZE) # Created: 10-JUN-2008 12:34:27 OBERFIEL # Fixed getFmtHeaderTime() routine and removed references to # TWEBs # # Revision 1.30 (DELIVERED) # Created: 14-MAR-2008 10:10:45 OBERFIEL # Adjusted visibility thresholds downward slightly in LIFR to # VLIFR categories # # Revision 1.29 (DELIVERED) # Created: 26-FEB-2008 14:25:35 OBERFIEL # Fixed _talk notification (unimplemented) and DTG for Nov # 2008 # # Revision 1.28 (DELIVERED) # Created: 25-MAY-2007 14:27:09 OBERFIEL # Update to support additional information in remarks # # Revision 1.27 (DELIVERED) # Created: 14-MAY-2007 10:04:48 OBERFIEL # Removed references to the obsolete prototype XTF product. # Allow decoder and encoder to format TAF in two different # ways. New format will be triggered by day and time to be # specified at a later date. # # Revision 1.26 (DELIVERED) # Created: 02-JUN-2006 10:28:08 TROJAN # spr 7160: changed regular expression for visibility # # Revision 1.25 (DELIVERED) # Created: 02-JUN-2006 09:04:21 TROJAN # spr 7159: changed regular expression for visibility # # Revision 1.24 (DELIVERED) # Created: 16-MAY-2006 10:36:40 TROJAN # spr 7148: fixes TEMPO without sky/visibility # # Revision 1.23 (DELIVERED) # Created: 09-MAY-2006 16:04:35 TROJAN # SPR 7145: fixes TEMPO without sky/visibility # # Revision 1.22 (DELIVERED) # Created: 09-MAR-2006 13:42:58 TROJAN # spr 7107: TEMPO elements fix # # Revision 1.21 (DELIVERED) # Created: 09-MAR-2006 12:48:49 TROJAN # fix in findIndex() for missing elements # # Revision 1.20 (DELIVERED) # Created: 13-FEB-2006 10:09:20 TROJAN # fix wrong indentantion of PROB groups # # Revision 1.19 (APPROVED) # Created: 23-JAN-2006 08:23:10 TROJAN # stdr 956 # # Revision 1.18 (APPROVED) # Created: 12-OCT-2005 18:27:31 TROJAN # spr 7039 # # Revision 1.17 (DELIVERED) # Created: 09-SEP-2005 13:53:20 TROJAN # spr 7011 # # Revision 1.16 (DELIVERED) # Created: 16-AUG-2005 13:53:04 TROJAN # spr 6988 # # Revision 1.15 (APPROVED) # Created: 09-AUG-2005 15:02:46 TROJAN # spr 6975 # # Revision 1.14 (DELIVERED) # Created: 29-JUL-2005 18:55:09 TROJAN # spr 6956 # # Revision 1.13 (APPROVED) # Created: 06-JUL-2005 21:01:41 TROJAN # spr 6909 # # Revision 1.12 (DELIVERED) # Created: 12-MAY-2005 14:05:53 TROJAN # spr 6833 # # Revision 1.11 (REVIEW) # Created: 07-MAY-2005 11:52:17 OBERFIEL # Added Item Header Block # # Revision 1.10 (DELIVERED) # Created: 04-APR-2005 15:20:32 TROJAN # spr 6780 # # Revision 1.9 (APPROVED) # Created: 02-APR-2005 17:02:15 TROJAN # spr 6763 # # Revision 1.8 (DELIVERED) # Created: 14-FEB-2005 20:54:46 TROJAN # spr 6649 # # Revision 1.7 (APPROVED) # Created: 23-JAN-2005 19:05:30 TROJAN # spr 6586 # # Revision 1.6 (APPROVED) # Created: 07-DEC-2004 14:13:59 TROJAN # spr 6521 # # Revision 1.5 (APPROVED) # Created: 16-NOV-2004 20:12:09 PCMS # Restoring history # # Revision 1.4 (DELIVERED) # Created: 19-MAR-2004 18:32:39 TROJAN # spr 5922 # # Revision 1.3 (DELIVERED) # Created: 15-JAN-2004 22:30:10 PCMS # Fixed AdjustTimes tool which didn't work in second half of # the month # # Revision 1.2 (DELIVERED) # Created: 08-JAN-2004 21:39:50 PCMS # Updating for code cleanup # # Revision 1.1 (APPROVED) # Created: 06-NOV-2003 16:45:19 OBERFIEL # date and time created -2147483647/-2147483648/-2147481748 # -2147483648:-2147483648:-2147483648 by oberfiel # # Change Document History: # 1: # Change Document: GFS1-NHD_SPR_7432 # Action Date: 06-NOV-2009 08:30:19 # Relationship Type: In Response to # Status: NEXTRELEASE # Title: OB9.2 AvnFPS - TPO/FuelAlternate Rule Doesn't work # # SOFTWARE HISTORY # # Date Ticket# Engineer Description # ------------- -------- --------- -------------------------------------------- # Aug 03, 2015 17540 zhao Modified to make default issue time configurable # Jan 16, 2018 6989 tgurney Change AMD line indent to 5 spaces # Mar 22, 2018 6742 njensen Handle SKC in fixTafSky() # Mar 10, 2022 8808 randerso Update ConfigParser to better work with # Java commons.configuration # ## # This is a base file that is not intended to be overridden. ## import os import time import Avn import AvnConfigParser # transmission times _Fcst_Times = (6 * 3600, 12 * 3600, 18 * 3600, 24 * 3600) _Xmit_Windows = (2400, 1200) ############################################################################### # common functions used by forecast editors ############################################################################### def _startHour(bbb, t): if bbb and bbb[0] != ' ': # 1731Z -> 1818 t += 1800.0 tsec = t % 86400.0 for i in range(len(_Fcst_Times)): if tsec < _Fcst_Times[i]: break else: i = 0 if bbb and bbb[0] != ' ': # not regular: previous period i -= 1 return (_Fcst_Times[i] // 3600) % 24 ############################################################################## def getFmtHeaderTime(bbb, t=None): # Returns header time string: one hour less than start of valid time if t is None: t = time.time() tms = list(time.gmtime(t)) try: if bbb[0] != ' ': tms[4:6] = 0, 0 else: raise IndexError except IndexError: hour = (_startHour(bbb, t) - 1) % 24 thour = tms[3] if thour < hour - 12: # previous day tms = list(time.gmtime(t - 86400.0)) elif thour > hour + 12: # next day tms = list(time.gmtime(t + 86400.0)) tms[3:6] = hour, 0, 0 return time.strftime('%d%H00', tuple(tms)) def getValidTime(bbb, t=None): # returns start of valid time if t is None: t = time.time() starthour = _startHour(bbb, t) if bbb and bbb[0] != ' ': # amendments and delayed forecasts: use closest hour starthour = int(((t + 1800.0) // 3600)) % 24 if starthour < time.gmtime(t)[3]: t += 86400.0 tms = list(time.gmtime(t)) tms[3:6] = starthour, 0, 0 return Avn.mkgmtime(tms) def getFmtValidTime(bbb, t=None, tafDuration=24, evtime=None): # Returns valid time as yymmddHHhh for 'bbb' if t is None: t = time.time() # # Get the ending day and hour first tms = list(time.gmtime(t)) tms[3:6] = _startHour(bbb, t), 0, 0 if tms[3] == 0: try: if bbb[0] == ' ': tms[2] += 1 except IndexError: tms[2] += 1 tms = list(time.gmtime(Avn.mkgmtime(tms) + tafDuration * 3600)) endday, endhour = tms[2:4] if tms[3] == 0: tms[3] = -1 endday, endhour = time.gmtime(Avn.mkgmtime(tms))[2], 24 # # for amendments and delayed forecasts: use closest hour if bbb and bbb[0] != ' ': starthour = int(((t + 1800.0) // 3600)) % 24 # # If amendment needed before valid period starts (up to 40 minutes # prior (2400s)), preserve previous ending valid time # if evtime and (_Fcst_Times[0] - t % _Fcst_Times[0]) < 2400: endday, endhour = int(evtime[:2]), int(evtime[2:]) # # Otherwise, find the next regular issuance time else: starthour = _startHour(bbb, t) tms = list(time.gmtime(t)) if starthour < tms[3]: t += 86400.0 tms = list(time.gmtime(t)) tms[3:6] = starthour, 0, 0 year, month, startday = tms[:3] return '%02d%02d%02d%02d/%02d%02d' % (year - 2000, month, startday, starthour, endday, endhour) def getIssueTime(bbb, t=None): if t is None: t = time.time() if not bbb or bbb[0] == ' ': # regular issue forecast itime = Avn.string2time('%s00' % getFmtValidTime(bbb, t)[:8]) minutesBeforeForecastTime = getMinutesBeforeForecastTime() if minutesBeforeForecastTime != None: itime -= 60 * int(minutesBeforeForecastTime) else: itime -= _Xmit_Windows[0] if itime > t: return itime return t def getMinutesBeforeForecastTime(): try: fname = os.path.join(Avn.ConfigDir, 'default_issue_time.cfg') f = Avn.PATH_MGR.getStaticFile(fname) fname = f.getPath() if not (os.path.exists(fname)): return None cp = AvnConfigParser.AvnConfigParser() cp.read(fname) d = cp.get('minutesBeforeForecastTime', 'minutes') return d except Exception: raise def getFmtIssueTime(bbb, t=None): if t is None: t = time.time() return time.strftime('%d%H%MZ', time.gmtime(getIssueTime(bbb, t))) ############################################################################### # functions used to produce forecasts ############################################################################## def _split_line(line, indent=0): # used by indentTaf() maxlen = 68 - indent if indent > 0: lst1 = [' ' * indent] else: lst1 = [] lst2 = [' ' * 5] total = 0 for word in line.split(): if total + len(word) < maxlen: lst1.append(word) else: lst2.append(word) total += len(word) + 1 return ' '.join(lst1), ' '.join(lst2).rstrip() def indentTaf(lines): taf = [] lines = [_f for _f in [x.strip() for x in lines] if _f] if not lines: return taf k = 0 if lines[0][:3] == 'TAF': taf.append(lines[0]) k += 1 l1, l2 = _split_line(lines[k]) taf.append(l1) if l2: taf.append(l2) k += 1 for line in lines[k:]: if line.startswith('FM') or line.startswith('AMD'): l1, l2 = _split_line(line, 4) elif line.startswith('TEMPO'): l1, l2 = _split_line(line, 5) elif line.startswith('PROB'): prevline = taf.pop() if taf: indent = 4 else: indent = 0 l1, l2 = _split_line('%s %s' % (prevline, line), indent) else: l1, l2 = _split_line(line, 5) taf.append(l1) if l2: taf.append(l2) if not taf[-1].endswith('='): taf[-1] += '=' return taf def adjustTimes(bbb, taf): # removes forecast periods that passed, adjust start of valid time if not 'group' in taf: # NIL TAF return stime = getValidTime(bbb) groups = [g for g in taf['group'] if g['prev']['time']['to'] > stime] for g in groups: if ('ocnl' in g and g['ocnl']['type'] == 'PROB' and g['ocnl']['time']['from'] < stime + 32400): del g['ocnl'] g = groups[0] if g['prev']['time']['from'] < stime: g['prev']['time']['from'] = stime if 'ocnl' in g: if g['ocnl']['time']['from'] < stime: if g['ocnl']['time']['to'] - stime < 3600.0: del g['ocnl'] else: g['ocnl']['time']['from'] = stime taf['group'] = groups def formatRecForMAIN(rec, ident, bbb, itime=None, tafDuration=24, evtime=None): # returns formated MAIN group as a list of strings lst = [ident, getFmtIssueTime(bbb, itime), getFmtValidTime(bbb, itime, tafDuration, evtime)[4:]] if not rec: # no data or too early for forecast return lst try: if 'wind' in rec: lst.append(rec['wind']['str']) if 'vsby' in rec: lst.append(rec['vsby']['str']) if 'pcp' in rec: lst.append(rec['pcp']['str']) if 'obv' in rec: lst.append(rec['obv']['str']) if 'vcnty' in rec: lst.append(rec['vcnty']['str']) if 'sky' in rec: lst.append(rec['sky']['str']) if 'llws' in rec: lst.append(rec['llws']['str']) except KeyError: pass return lst def formatRecForFM(rec): # returns formatted FM group as a list of strings if not rec: return [] lst = [time.strftime('FM%d%H%M', time.gmtime(rec['time']['from']))] try: lst.extend([rec[k]['str'] for k in ['wind', 'vsby', 'pcp', 'obv', 'vcnty', 'sky', 'llws'] if k in rec]) except KeyError: pass return lst def formatRecForOCNL(rec): # returns formated TEMPO group as a list of strings # or an empty list if TEMPO not needed if not rec or rec['type'] not in ['TEMPO', 'PROB']: return [] start = time.gmtime(rec['time']['from']) end = list(time.gmtime(rec['time']['to'])) if end[3] == 0: end[2] = start[2] end[3] = 24 lst = [] typ = rec['type'] if typ == 'TEMPO': lst = ['TEMPO'] elif typ == 'PROB': lst = ['PROB30'] if len(lst): lst.append('%02d%02d/%02d%02d' % (start[2], start[3], end[2], end[3])) try: lst.extend([rec[k]['str'] for k in ['wind', 'vsby', 'pcp', 'obv', 'nsw', 'sky'] if k in rec]) except KeyError: pass if len(lst) > 1: return lst else: return [] def _filterPeriods(bbb, periods, t, tafDuration): starttime = getValidTime(bbb, t) tms = list(time.gmtime(starttime)) tms[3] = _startHour(bbb, t) endtime = Avn.mkgmtime(tms) if endtime <= starttime: endtime += tafDuration * 3600.0 def _within(p): return (p['prev']['time']['to'] > starttime and p['prev']['time']['from'] < endtime) return filter(_within, periods) def makeTafFromPeriods(ident, bbb, periods, t=None, tafDuration=24, evtime=None): # returns formatted TAF given sequence of periods if bbb is None: bbb = 'RRA' if t is None: t = time.time() if bbb.startswith('A'): lines = ['TAF AMD'] else: lines = ['TAF'] inperiods = _filterPeriods(bbb, periods, t, tafDuration) try: p = next(inperiods) if getValidTime(bbb, t) < p['prev']['time']['from'] - 1800.0: # start time of the first group in the future tmp = formatRecForMAIN(None, ident, bbb, t, tafDuration, evtime) lines.append(' '.join(' '.join(tmp).split())) tmp = formatRecForFM(p['prev']) else: tmp = formatRecForMAIN(p['prev'], ident, bbb, t, tafDuration, evtime) if not tmp: raise StopIteration lines.append(' '.join(' '.join(tmp).split())) if 'ocnl' in p: tmp = formatRecForOCNL(p['ocnl']) if tmp: lines.append(' '.join(' '.join(tmp).split())) while 1: p = next(inperiods) tmp = formatRecForFM(p['prev']) lines.append(' '.join(' '.join(tmp).split())) if 'ocnl' in p: tmp = formatRecForOCNL(p['ocnl']) if tmp: lines.append(' '.join(' '.join(tmp).split())) except StopIteration: pass return lines # default categories, used for eliminating unneccessary weather from TEMPO _VsbyCat = [0.5, 1.0, 2.0, 3.0, 5.0] _CigCat = [2, 6, 10, 20, 31] def fixTafVsby(v): # returns valid TAF visibility as a dictionary {val, str} if v < 0.0: return {} elif v < 0.12: return {'value': 0.0, 'str': '0SM'} elif v < 0.37: return {'value': 0.25, 'str': '1/4SM'} elif v < 0.62: return {'value': 0.5, 'str': '1/2SM'} elif v < 0.87: return {'value': 0.75, 'str': '3/4SM'} elif v < 1.3: return {'value': 1.0, 'str': '1SM'} elif v < 1.8: return {'value': 1.5, 'str': '1 1/2SM'} elif v < 6.5: iv = (v + 0.49) // 1.0 return {'value': iv, 'str': '%.0fSM' % iv} else: return {'value': 12.0, 'str': 'P6SM'} def fixCldBase(h): # returns valid TAF cloud base if h < 30: return int(h) if h < 50: return int((h + 2) // 5 * 5) return int((h + 5) // 10 * 10) def fixTafSky(sky): # returns valid TAF sky conditions as a tuple (cig, string) # replaces CLR by SKC, rounds cloud bases _Cover = {'FEW': 1, 'SCT': 2, 'BKN': 3, 'OVC': 4} nsky = [] s = sky['str'] if s.find('CLR') != -1 or s.find('SKC') != -1: return {'cig': Avn.UNLIMITED, 'str': 'SKC'} elif s.find('VV') != -1: return sky cover = 0 for c, h, cb in [(x[:3], int(x[3:6]), x[6:]) for x in s.split()]: if cb == 'TCU': cb = '' cover = max(cover, _Cover.get(c, 0)) nsky.append('%s%03d%s' % (c, fixCldBase(h), cb)) return {'cig': fixCldBase(sky['cig']), 'cover': sky['cover'], 'str': ' '.join(nsky)} def updateTafWithMetar(tafgrp, mtr): # replaces TAF group with METAR data if 'vsby' in mtr: tafgrp['vsby'] = fixTafVsby(mtr['vsby']['value']) if 'sky' in mtr: tafgrp['sky'] = fixTafSky(mtr['sky']) for k in ['wind', 'pcp', 'obv']: if k in mtr: tafgrp[k] = mtr[k] elif k in tafgrp: del tafgrp[k] def fixTafTempo(p, t): # eliminates TAF TEMPO elements that match those in FM group if not ('obv' in t or 'pcp' in t) and 'vsby' in t: tcat = Avn.category(t['vsby']['val'], _VsbyCat) pcat = Avn.category(p['vsby']['val'], _VsbyCat) if tcat == pcat: del t['vsby'] else: t['obv'] = {'str': 'NSW'} if 'wind' in t: tdd, tff = t['wind']['dd'], t['wind']['ff'] pdd, pff = p['wind']['dd'], p['wind']['ff'] if Avn.VARIABLE in (tdd, pdd): delta = 0 else: delta = abs(pdd - tdd) if delta > 180: delta = 360 - delta if abs(tff - pff) < 10 and (max(tff, pff) < 12 or delta < 30): del t['wind'] if 'sky' in t: tcat = Avn.category(t['sky']['cig'], _CigCat) pcat = Avn.category(p['sky']['cig'], _CigCat) if tcat == pcat: del t['sky'] ############################################################################### # utility functions ############################################################################## def getGroupIndex(g): ixlist = [g[i]['index'] for i in g if 'index' in g[i]] line = int(ixlist[0][0].split('.')[0]) fchar = min([int(x[0].split('.')[1]) for x in ixlist]) lchar = max([int(x[1].split('.')[1]) for x in ixlist]) return '%d.%d' % (line, fchar), '%d.%d' % (line, lchar) def getTafPeriods(taf): periods = [] indices = [] for p in taf['group'][:]: pp = p['prev'] periods.append(pp) indices.append(getGroupIndex(pp)) if 'ocnl' in p: po = pp.copy() po.update(p['ocnl']) periods.append(po) indices.append(getGroupIndex(p['ocnl'])) return list(zip(indices, periods)) def flightCategory(dcd): # dcd: dictionary d = {'cig': (cig, ...), 'vsby': (vsby, ...)} sky, vis = dcd.get('sky', {}), dcd.get('vsby', {}) cig, vsby = sky.get('cig', -1), vis.get('value', -1) if cig < 0: #cig is missing, vsby may be valid if vsby < 0: #vsby missing, default to VFR return Avn.VFR elif vsby < 1.0: return Avn.LIFR elif vsby < 3.0: return Avn.IFR elif vsby < 6.0: return Avn.MVFR return Avn.VFR elif vsby < 0: #vsby is missing, cig is valid (took care of cig & vsby both missing already) if cig < 500: return Avn.LIFR elif cig < 1000: return Avn.IFR elif cig < 3100: return Avn.MVFR return Avn.VFR elif cig < 500 or vsby < 1.0: return Avn.LIFR elif cig < 1000 or vsby < 3.0: return Avn.IFR elif cig < 3100 or vsby < 6.0: return Avn.MVFR else: return Avn.VFR ############################################################################## # functions used by monitors ############################################################################## def findIndex(e, dcd, hlen): def _update(ix): if hlen == 0: return ix l0, c0 = ix[0].split('.') l1, c1 = ix[1].split('.') return '%d.%s' % (int(l0) + hlen, c0), '%d.%s' % (int(l1) + hlen, c1) if e == 'wx': ix = [] if 'pcp' in dcd: ix.append(_update(dcd['pcp']['index'])) if 'obv' in dcd: ix.append(_update(dcd['obv']['index'])) return ix elif e == 'cat': ix = [] if 'vsby' in dcd: ix.append(_update(dcd['vsby']['index'])) if 'sky' in dcd: ix.append(_update(dcd['sky']['index'])) return ix elif e in dcd: return [_update(dcd[e]['index'])] else: return [] def _makeWx(g): """Returns wx string""" try: return ' '.join([g[k]['str'] for k in ['pcp', 'obv'] if k in g]) or None except KeyError: return None def makeMetarData(metar): d = {'time': metar['itime']['value']} try: tmp = metar['wind'] d['wind'] = {'dd': tmp['dd'], 'ff': {'lo': tmp['ff'], 'hi': tmp.get('gg', tmp['ff'])}} except KeyError: pass try: d['vsby'] = {'vsby': metar['vsby']['value']} except KeyError: pass s = _makeWx(metar) if s: d['wx'] = {'str': s} try: d['sky'] = {'cig': metar['sky']['cig']} except KeyError: pass # # Optional information from remarks section try: d['vvsby'] = {'lo': metar['vvsby']['lo'], 'hi': metar['vvsby']['hi']} except KeyError: pass try: d['vsky'] = {'cvr1': metar['vsky']['cvr1'], 'cvr2': metar['vsky']['cvr2'], 'cig': metar['vsky']['cig']} except KeyError: pass try: d['vcig'] = {'lo': metar['vcig']['lo'], 'hi': metar['vcig']['hi']} except KeyError: pass return d ############################################################################## # TAF structure used by monitors ############################################################################## class TafData: def _makeTafWind(g, items): d = {} try: if 'p' in items and 'wind' in g['prev']: tmp = g['prev']['wind'] d['dd'] = {'prev': tmp['dd']} d['ff'] = {'prev': tmp.get('gg', tmp['ff'])} if 'o' in items and g['ocnl'] and 'wind' in g['ocnl']: tmp = g['ocnl']['wind'] if 'dd' in d: d['dd']['ocnl'] = tmp['dd'] else: d['dd'] = {'ocnl': tmp['dd']} if 'ff' in d: d['ff']['ocnl'] = tmp.get('gg', tmp['ff']) else: d['ff'] = {'ocnl': tmp.get('gg', tmp['ff'])} lo = d['ff'].get('prev', 0) hi = d['ff'].get('ocnl', lo) if lo > hi: lo, hi = hi, lo d['ff']['lo'] = lo d['ff']['hi'] = hi except KeyError: pass return d _makeTafWind = staticmethod(_makeTafWind) def _makeTafVsby(g, items): d = {} try: if 'p' in items and 'vsby' in g['prev']: d['hi'] = g['prev']['vsby']['value'] d['prev'] = g['prev']['vsby']['value'] if 'o' in items and 'ocnl' in g: if 'vsby' in g['ocnl']: d['lo'] = g['ocnl']['vsby']['value'] d['ocnl'] = g['ocnl']['vsby']['value'] elif 'vsby' in g['prev']: d['hi'] = g['prev']['vsby']['value'] d['prev'] = g['prev']['vsby']['value'] else: return d if 'hi' in d and not 'lo' in d: d['lo'] = d['hi'] if 'lo' in d and not 'hi' in d: d['hi'] = d['lo'] if d['lo'] > d['hi']: d['lo'], d['hi'] = d['hi'], d['lo'] except KeyError: pass return d _makeTafVsby = staticmethod(_makeTafVsby) def _makeTafWx(g, items): pstr, ostr = None, None if 'p' in items: pstr = _makeWx(g['prev']) if 'o' in items and 'ocnl' in g: g = g['ocnl'] if 'nsw' in g: ostr = None elif 'pcp' in g or 'obv' in g: ostr = _makeWx(g) elif pstr: ostr = pstr d = {} if pstr: d['pstr'] = pstr if ostr: d['ostr'] = ostr return d _makeTafWx = staticmethod(_makeTafWx) def _makeTafSky(g, items): d = {} try: if 'p' in items and 'sky' in g['prev']: d['hi'] = g['prev']['sky']['cig'] d['prev'] = g['prev']['sky']['cig'] if 'o' in items: if 'ocnl' in g and 'sky' in g['ocnl']: d['lo'] = g['ocnl']['sky']['cig'] d['ocnl'] = g['ocnl']['sky']['cig'] elif 'sky' in g['prev']: d['hi'] = g['prev']['sky']['cig'] d['prev'] = g['prev']['sky']['cig'] else: return d if 'hi' in d and not 'lo' in d: d['lo'] = d['hi'] if 'lo' in d and not 'hi' in d: d['hi'] = d['lo'] if d['lo'] > d['hi']: d['lo'], d['hi'] = d['hi'], d['lo'] except KeyError: pass return d _makeTafSky = staticmethod(_makeTafSky) def _makeTafCover(g, items): d = {} try: if 'p' in items and 'sky' in g['prev']: d['hi'] = g['prev']['sky']['cover'] d['prev'] = g['prev']['sky']['cover'] if 'o' in items: if 'ocnl' in g and 'sky' in g['ocnl']: d['lo'] = g['ocnl']['sky']['cover'] d['ocnl'] = g['ocnl']['sky']['cover'] elif 'sky' in g['prev']: d['hi'] = g['prev']['sky']['cover'] d['prev'] = g['prev']['sky']['cover'] else: return d if 'hi' in d and not 'lo' in d: d['lo'] = d['hi'] if 'lo' in d and not 'hi' in d: d['hi'] = d['lo'] if d['lo'] > d['hi']: d['lo'], d['hi'] = d['hi'], d['lo'] except KeyError: pass return d _makeTafCover = staticmethod(_makeTafCover) def _makeTafTS(g, items): d = {} try: if 'p' in items: if 'pcp' in g['prev'] and 'TS' in g['prev']['pcp']['str']: d['prev'] = 'Y' elif 'vcnty' in g['prev'] and 'TS' in g['prev']['vcnty']['str']: d['prev'] = 'VC' if 'o' in items and 'ocnl' in g: if 'pcp' in g['ocnl'] and 'TS' in g['ocnl']['pcp']['str']: d['ocnl'] = g['ocnl']['type'] except KeyError: pass return d _makeTafTS = staticmethod(_makeTafTS) def _makeTafCB(g, items): d = {} try: if 'p' in items and g['prev']['sky']['str'].find('CB') > -1: d['prev'] = 'Y' raise KeyError if 'o' in items and 'ocnl' in g and g['ocnl']['sky']['str'].find('CB') > -1: d['prev'] = 'Y' except KeyError: pass return d _makeTafCB = staticmethod(_makeTafCB) def _makeTafLLWS(g, items): d = {} if 'p' in items: if 'llws' in g['prev']: d['prev'] = True return d _makeTafLLWS = staticmethod(_makeTafLLWS) def _make(g, t1, t2, items): d = {} for key, f in [('wind', TafData._makeTafWind), ('vsby', TafData._makeTafVsby), ('wx', TafData._makeTafWx), ('sky', TafData._makeTafSky), ('cover', TafData._makeTafCover), ('ts', TafData._makeTafTS), ('cb', TafData._makeTafCB), ('llws', TafData._makeTafLLWS)]: tmp = f(g, items) if tmp: d[key] = tmp d['from'], d['to'] = t1, t2 return d _make = staticmethod(_make) def makeTempo(grp): try: to1, to2 = grp['ocnl']['time']['from'], grp['ocnl']['time']['to'] tempo = TafData._make(grp, to1, to2, ['o']) tempo['tempoCheck'] = True # # Promote TEMPO items to prevailing try: tempo['wx']['pstr'] = tempo['wx']['ostr'] del tempo['wx']['ostr'] except KeyError: pass for v in tempo.values(): try: v['prev'] = v['ocnl'] del v['ocnl'] except (TypeError, KeyError): pass return tempo except KeyError: return None makeTempo = staticmethod(makeTempo) def __init__(self, groups): self._data = [] for g in groups: tp1, tp2 = g['prev']['time']['from'], g['prev']['time']['to'] if 'ocnl' in g: to1, to2 = g['ocnl']['time']['from'], g['ocnl']['time']['to'] if tp1 < to1: d = TafData._make(g, tp1, to1, ['p']) self._data.append(d) tp1 = to1 d = TafData._make(g, tp1, to2, ['p', 'o']) self._data.append(d) if to2 < tp2: d = TafData._make(g, to2, tp2, ['p']) self._data.append(d) else: d = TafData._make(g, tp1, tp2, ['p']) self._data.append(d) def get(self, t): if t < self._data[0]['from'] or t >= self._data[-1]['to']: return None for d in self._data: if d['from'] <= t < d['to']: return d return None