Change-Id: If95cb839ad81ca2a842ff7f6926847ac3928d8f2 Former-commit-id: 77e1a4d8f5237e5fae930c1e00589c752f8b3738
562 lines
19 KiB
Python
562 lines
19 KiB
Python
##
|
|
# 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:
|
|
# MetarViewer.py
|
|
# GFS1-NHD:A6637.0000-SCRIPT;1.21
|
|
#
|
|
# Status:
|
|
# DELIVERED
|
|
#
|
|
# History:
|
|
# Revision 1.21 (DELIVERED)
|
|
# Created: 30-OCT-2007 14:19:26 OBERFIEL
|
|
# Removed CR characters
|
|
#
|
|
# Revision 1.20 (REVIEW)
|
|
# Created: 26-OCT-2007 09:33:59 GILMOREDM
|
|
# Related to GFS1-NHD_SPR_7324
|
|
#
|
|
# Revision 1.19 (INITIALIZE)
|
|
# Created: 26-OCT-2007 09:32:03 GILMOREDM
|
|
# Added functionality that allows forecasters to determine
|
|
# the number of observations shown by default when "all" TAF
|
|
# sites are shown in the metar viewer window.
|
|
#
|
|
# Revision 1.18 (DELIVERED)
|
|
# Created: 20-JUN-2007 08:56:55 OBERFIEL
|
|
# Updates to take care of PIT DRs
|
|
#
|
|
# Revision 1.17 (DELIVERED)
|
|
# Created: 19-SEP-2006 06:46:39 OBERFIEL
|
|
# Added VC types to display
|
|
#
|
|
# Revision 1.16 (DELIVERED)
|
|
# Created: 23-FEB-2006 14:40:59 TROJAN
|
|
# multiple METAR sites were not processed
|
|
#
|
|
# Revision 1.15 (DELIVERED)
|
|
# Created: 06-FEB-2006 09:53:24 TROJAN
|
|
# missing check for undefined alternate sites
|
|
#
|
|
# Revision 1.14 (APPROVED)
|
|
# Created: 31-JAN-2006 18:14:19 TROJAN
|
|
# spr 7081
|
|
#
|
|
# Revision 1.13 (APPROVED)
|
|
# Created: 23-JAN-2006 08:23:15 TROJAN
|
|
# stdr 956
|
|
#
|
|
# Revision 1.12 (APPROVED)
|
|
# Created: 12-OCT-2005 18:26:36 TROJAN
|
|
# spr 7040
|
|
#
|
|
# Revision 1.11 (DELIVERED)
|
|
# Created: 06-JUL-2005 18:16:40 TROJAN
|
|
# spr 6548
|
|
#
|
|
# Revision 1.10 (DELIVERED)
|
|
# Created: 07-MAY-2005 11:36:07 OBERFIEL
|
|
# Added Item Header Block
|
|
#
|
|
# Revision 1.9 (DELIVERED)
|
|
# Created: 04-APR-2005 14:47:32 TROJAN
|
|
# spr 6781
|
|
#
|
|
# Revision 1.8 (APPROVED)
|
|
# Created: 02-APR-2005 17:47:24 TROJAN
|
|
# spr 6772
|
|
#
|
|
# Revision 1.7 (DELIVERED)
|
|
# Created: 28-FEB-2005 21:37:47 TROJAN
|
|
# spr 6686
|
|
#
|
|
# Revision 1.6 (DELIVERED)
|
|
# Created: 19-AUG-2004 20:04:11 OBERFIEL
|
|
# Bug fixes
|
|
#
|
|
# Revision 1.5 (APPROVED)
|
|
# Created: 09-JUL-2004 19:11:35 OBERFIEL
|
|
# Replaced busy dialogs
|
|
#
|
|
# Revision 1.4 (APPROVED)
|
|
# Created: 01-JUL-2004 14:59:43 OBERFIEL
|
|
# Update
|
|
#
|
|
# Revision 1.3 (DELIVERED)
|
|
# Created: 06-FEB-2004 15:53:43 OBERFIEL
|
|
# Fixed MOFF error
|
|
#
|
|
# Revision 1.2 (DELIVERED)
|
|
# Created: 08-JAN-2004 21:40:11 PCMS
|
|
# Updating for code cleanup
|
|
#
|
|
# Revision 1.1 (APPROVED)
|
|
# Created: 06-NOV-2003 16:45:58 OBERFIEL
|
|
# date and time created -2147483647/-2147483648/-2147481748
|
|
# -2147483648:-2147483648:-2147483648 by oberfiel
|
|
#
|
|
# Change Document History:
|
|
# 1:
|
|
# Change Document: GFS1-NHD_SPR_7324
|
|
# Action Date: 19-MAR-2008 07:58:32
|
|
# Relationship Type: In Response to
|
|
# Status: CLOSED
|
|
# Title: AvnFPS: Number of obs shown in TAF Editor/Viewer dependent on 'All' button
|
|
#
|
|
#
|
|
import logging, re, time
|
|
import AvnLib, AvnParser, Globals, MetarMonitor
|
|
|
|
TAG = 'warning'
|
|
sfcvis = re.compile(r'SFC\s+VIS\s+((\d{1,2}(?!/)\s*)?([1357]/1?[2468])?)')
|
|
|
|
_Logger = logging.getLogger(__name__)
|
|
|
|
##############################################################################
|
|
# Local functions used to create strings for elements in Items below
|
|
def _makeType(dcd):
|
|
return dcd['type']['str'][0]
|
|
|
|
def _makeTime(dcd):
|
|
return dcd['itime']['str'][:-1]
|
|
|
|
def _makeSky(dcd):
|
|
if 'sky' in dcd:
|
|
return ' '.join(dcd['sky']['str'].split())
|
|
else:
|
|
return ''
|
|
|
|
def _makeVsby(dcd):
|
|
if 'vsby' in dcd:
|
|
vsbystr = dcd['vsby']['str']
|
|
#
|
|
if vsbystr.endswith('SM'):
|
|
return vsbystr[:-2]
|
|
#
|
|
# If SFC VIS is found in remarks strip that off and
|
|
# any extraneous new line and whitespaces
|
|
#
|
|
else:
|
|
re_result = sfcvis.match(vsbystr)
|
|
if re_result:
|
|
return ' '.join(re_result.group(1).split())
|
|
else:
|
|
return vsbystr
|
|
else:
|
|
return ''
|
|
|
|
def _makeWx(dcd):
|
|
tmp = []
|
|
if 'pcp' in dcd:
|
|
if dcd['pcp']['str'][0] not in ('+', '-'):
|
|
tmp.append(' ' + dcd['pcp']['str'])
|
|
else:
|
|
tmp.append(dcd['pcp']['str'])
|
|
if 'obv' in dcd:
|
|
tmp.append(dcd['obv']['str'])
|
|
|
|
if 'vcnty' in dcd:
|
|
tmp.append(dcd['vcnty']['str'])
|
|
|
|
if len(tmp) == 0 :
|
|
return ''
|
|
return ' '.join(tmp)
|
|
|
|
def _makeSlp(dcd):
|
|
if 'mslp' in dcd:
|
|
return dcd['mslp']['str'][3:]
|
|
else:
|
|
return ''
|
|
|
|
def _makeTemp(dcd):
|
|
if 'tempdec' in dcd and 'tt' in dcd['tempdec']:
|
|
return '%3.0f' % (dcd['tempdec']['tt']*9.0/5.0+32.0)
|
|
elif 'temp' in dcd and 'tt' in dcd['temp']:
|
|
return '%3.0f' % (dcd['temp']['tt']*9.0/5.0+32.0)
|
|
else:
|
|
return ''
|
|
|
|
def _makeDewp(dcd):
|
|
if 'tempdec' in dcd and 'td' in dcd['tempdec']:
|
|
return '%3.0f' % (dcd['tempdec']['td']*9.0/5.0+32.0)
|
|
elif 'temp' in dcd and 'td' in dcd['temp']:
|
|
return '%3.0f' % (dcd['temp']['td']*9.0/5.0+32.0)
|
|
else:
|
|
return ''
|
|
|
|
def _makeWind(dcd):
|
|
if 'wind' in dcd:
|
|
return dcd['wind']['str'][:-2]
|
|
else:
|
|
return ''
|
|
|
|
def _makeAltim(dcd):
|
|
if 'alt' in dcd:
|
|
return dcd['alt']['str'][2:]
|
|
else:
|
|
return ''
|
|
|
|
def _makePcp1h(dcd):
|
|
if 'pcp1h' in dcd:
|
|
return dcd['pcp1h']['str'][1:]
|
|
else:
|
|
return ''
|
|
|
|
##############################################################################
|
|
# Items is a list of element dictionaries, each consists of
|
|
# item, length, left-alignment-flag: either '' or '-', header
|
|
# (always left-aligned), output-function (defined above)
|
|
# sky, vsby, wx and wind are mandatory items
|
|
##############################################################################
|
|
Items = [ \
|
|
{'item': 'type', 'length': 1, 'left': '', \
|
|
'header': 'T', 'fun': _makeType, \
|
|
'startTag' : '', 'endTag' : '', \
|
|
'adjustLength' : False}, \
|
|
{'item': 'time', 'length': 6, 'left': '', \
|
|
'header': 'TIME', 'fun': _makeTime, \
|
|
'startTag' : '', 'endTag' : '', \
|
|
'adjustLength' : False}, \
|
|
{'item': 'wind', 'length': 8, 'left': '-', \
|
|
'header': 'WIND', 'fun': _makeWind, \
|
|
'startTag' : '<wind>', 'endTag' : '</wind>', \
|
|
'adjustLength' : False}, \
|
|
{'item': 'vsby', 'length': 5, 'left': '-', \
|
|
'header': 'VSBY', 'fun': _makeVsby, \
|
|
'startTag' : '<vsby>', 'endTag' : '</vsby>', \
|
|
'adjustLength' : False}, \
|
|
{'item': 'wx', 'length': 11, 'left': '-', \
|
|
'header': 'WX', 'fun': _makeWx, \
|
|
'startTag' : '<wx>', 'endTag' : '</wx>', \
|
|
'adjustLength' : True}, \
|
|
{'item': 'sky', 'length': 13, 'left': '-', \
|
|
'header': 'SKY CONDITION', 'fun': _makeSky, \
|
|
'startTag' : '<sky>', 'endTag' : '</sky>', \
|
|
'adjustLength' : True}, \
|
|
{'item': 'slp', 'length': 3, 'left': '', \
|
|
'header': 'SLP', 'fun': _makeSlp, \
|
|
'startTag' : '', 'endTag' : '', \
|
|
'adjustLength' : False}, \
|
|
{'item': 'temp', 'length': 3, 'left': '', \
|
|
'header': ' TT', 'fun': _makeTemp, \
|
|
'startTag' : '', 'endTag' : '', 'adjustLength' : False}, \
|
|
{'item': 'dewp', 'length': 3, 'left': '', \
|
|
'header': ' DP', 'fun': _makeDewp, \
|
|
'startTag' : '', 'endTag' : '', \
|
|
'adjustLength' : False}, \
|
|
{'item': 'altim', 'length': 3, 'left': '', \
|
|
'header': 'ALT', 'fun': _makeAltim, \
|
|
'startTag' : '', 'endTag' : '', \
|
|
'adjustLength' : False}, \
|
|
{'item': 'pcp1h', 'length': 4, 'left': '', \
|
|
'header': 'PCP', 'fun': _makePcp1h, \
|
|
'startTag' : '', 'endTag' : '', \
|
|
'adjustLength' : False}, \
|
|
]
|
|
|
|
##############################################################################
|
|
def _makeIndices():
|
|
sky, vsby, wx, wind = 0, 0, 0, 0
|
|
ix = {}
|
|
for it in Items:
|
|
if it['item'] == 'sky':
|
|
ix['sky'] = ['1.%d' % sky, '1.%d' % (sky+it['length'])]
|
|
else:
|
|
sky += it['length'] + 1
|
|
if it['item'] == 'vsby':
|
|
ix['vsby'] = ['1.%d' % vsby, '1.%d' % (vsby+it['length'])]
|
|
else:
|
|
vsby += it['length'] + 1
|
|
if it['item'] == 'wx':
|
|
ix['wx'] = ['1.%d' % wx, '1.%d' % (wx+it['length'])]
|
|
else:
|
|
wx += it['length'] + 1
|
|
if it['item'] == 'wind':
|
|
ix['wind'] = ['1.%d' % wind, '1.%d' % (wind+it['length'])]
|
|
else:
|
|
wind += it['length'] + 1
|
|
return ix
|
|
|
|
def _makeHeaderLine():
|
|
return ' '.join(['%%-%ds' % it['length'] % it['header'] for it in Items])
|
|
|
|
def _adjustLengths(data):
|
|
for item in Items:
|
|
if item['adjustLength']:
|
|
maxLen = len(item['header'])
|
|
fun = item['fun']
|
|
for d in data:
|
|
length = len(fun(d['dcd']))
|
|
if maxLen < length :
|
|
maxLen = length
|
|
item['length'] = maxLen
|
|
|
|
##############################################################################
|
|
class Viewer(object):
|
|
def _format(dcd, addTages = False):
|
|
if addTages :
|
|
return '%s' % ' '.join(['%%s%%%s%ds%%s' % (it['left'], \
|
|
it['length']) % \
|
|
(it['startTag'], it['fun'](dcd), it['endTag']) for it in Items])
|
|
else:
|
|
return '%s' % ' '.join(['%%%s%ds' % (it['left'], it['length']) % \
|
|
it['fun'](dcd) for it in Items])
|
|
_format = staticmethod(_format)
|
|
|
|
def __init__(self, master, tafviewer, **kw):
|
|
# master: parent widget (page in a notebook)
|
|
# getcmd: data access method, returnig dictionary
|
|
# d = {'text': text, 'dcd': decoded, 'wind': (), 'vsby': (),
|
|
# 'wx': (), 'cig': ()}
|
|
# tafviewer: TafViewer instance
|
|
self._master = master
|
|
self._id = None
|
|
self._mtrid = None
|
|
self._ids = []
|
|
self._data = None
|
|
self._getcmd = kw['command']
|
|
self.messagebar = kw['messagebar']
|
|
self._tafviewer = tafviewer
|
|
|
|
self._header = _makeHeaderLine() # decoded METARs
|
|
self._index = _makeIndices() # for highlightning
|
|
|
|
self._tkNumHours = IntVar()
|
|
self._tkNumHours.set(int(master.option_get('numHours', '')))
|
|
self._tkNumHoursHold = IntVar()
|
|
self._tkNumHoursAll = IntVar()
|
|
self._tkNumHoursAll.set(int(master.option_get('numHoursAll', '')))
|
|
self._tkShowDecoded = IntVar()
|
|
self._tkShowDecoded.set(int(master.option_get('showDecoded', '')))
|
|
self._tkShowHeaders = IntVar()
|
|
self._tkShowHeaders.set(int(master.option_get('showHeaders', '')))
|
|
self._tkHighlightFlightCat = IntVar()
|
|
self._tkHighlightFlightCat.set(int(master.option_get( \
|
|
'highlightFlightCat', '')))
|
|
self._tkAll = IntVar()
|
|
|
|
frame = Frame(master)
|
|
menu = Pmw.OptionMenu(frame,
|
|
labelpos='w',
|
|
label_text='Num Hours',
|
|
menubutton_width=3,
|
|
menubutton_textvariable=self._tkNumHours,
|
|
items=('1', '3', '6', '12', '24', '99'),
|
|
command=self.load,
|
|
)
|
|
menu.pack(side='right', expand='no', fill='x', padx=2)
|
|
Balloon().bind(menu, 'Number of hours to display')
|
|
cb = Checkbutton(frame,
|
|
text='Decoded',
|
|
variable=self._tkShowDecoded,
|
|
command=self.load,
|
|
)
|
|
cb.pack(side='right', padx=5)
|
|
Balloon().bind(cb, 'Display decoded obs ARONET style')
|
|
cb = Checkbutton(frame,
|
|
text='Headers',
|
|
variable=self._tkShowHeaders,
|
|
command=self.load,
|
|
)
|
|
cb.pack(side='right', padx=5)
|
|
Balloon().bind(cb, 'Display WMO header for raw reports')
|
|
cb = Checkbutton(frame,
|
|
text='Flight Categories',
|
|
variable=self._tkHighlightFlightCat,
|
|
command=self.load,
|
|
)
|
|
cb.pack(side='right', padx=5)
|
|
Balloon().bind(cb, 'Background color depicts flight categories')
|
|
cb = Checkbutton(frame,
|
|
text='All',
|
|
variable=self._tkAll,
|
|
command=self.setAll,
|
|
)
|
|
cb.pack(side='right', padx=5)
|
|
Balloon().bind(cb, 'Displays data for all sites')
|
|
frame.pack(side='top', expand='no', fill='x')
|
|
|
|
self.text = Pmw.ScrolledText(master,
|
|
columnheader=1,
|
|
columnheader_name='textViewer',
|
|
borderframe=1,
|
|
vscrollmode='static',
|
|
hscrollmode='dynamic',
|
|
text_state='disabled',
|
|
text_wrap='none',
|
|
text_setgrid='true',
|
|
text_name='textViewer',
|
|
)
|
|
self.text.pack(side='top', expand='yes', fill='both')
|
|
self.text.tag_configure(TAG, background='red')
|
|
self.text.tag_raise(TAG)
|
|
self.text.component('text').bind('<Button-3>', self.__popupMenu)
|
|
self.popmenu = Menu(master,
|
|
tearoff=0,
|
|
type='normal',
|
|
)
|
|
self.popmenu.add_command(label='Copy', command=self.__copy)
|
|
|
|
AvnLib.configureFlightCatColors(self.text)
|
|
|
|
def __copy(self):
|
|
try:
|
|
t = self.text.component('text')
|
|
t.selection_own()
|
|
selection = t.selection_get()
|
|
t.clipboard_clear()
|
|
t.clipboard_append(selection)
|
|
except:
|
|
pass
|
|
|
|
def __popupMenu(self, e):
|
|
self.popmenu.tk_popup(e.widget.winfo_rootx() + e.x,
|
|
e.widget.winfo_rooty() + e.y)
|
|
|
|
def __highlightDecoded(self):
|
|
for e in self._data['status']:
|
|
if self._data['status'][e].severity < 2:
|
|
continue
|
|
if e == 'tempo':
|
|
pass
|
|
elif e == 'cat':
|
|
for e1 in ['vsby', 'sky']:
|
|
self.text.tag_add(TAG, *self._index[e1])
|
|
else:
|
|
self.text.tag_add(TAG, *self._index[e])
|
|
|
|
def __highlightRaw(self):
|
|
if self._tkShowHeaders.get():
|
|
hlen = self._data['header'].count('\n')
|
|
else:
|
|
hlen = 0
|
|
dcd = self._data['dcd']
|
|
for e in self._data['status']:
|
|
if self._data['status'][e].severity < 2:
|
|
continue
|
|
for ix in AvnLib.findIndex(e, dcd, hlen):
|
|
self.text.tag_add(*(TAG,)+ix)
|
|
|
|
def __showDecoded(self):
|
|
htw = self.text.component('columnheader')
|
|
htw.configure(state='normal')
|
|
htw.delete('1.0', 'end')
|
|
htw.insert('1.0', self._header)
|
|
htw.configure(state='disabled')
|
|
showfc = self._tkHighlightFlightCat.get()
|
|
if 'dcd' in self._data:
|
|
formatted = self._format(self._data['dcd'])
|
|
self.text.insert('end', formatted)
|
|
self.__highlightDecoded()
|
|
if showfc:
|
|
fcat = AvnLib.flightCategory(self._data['dcd'])
|
|
self.text.tag_add(fcat, 1.0, 2.0)
|
|
ftime = time.time() - 3600.0*self._tkNumHours.get()
|
|
for n, metar in enumerate([x for x in Globals.DRC.getMetars( \
|
|
self._mtrid, True, ftime, 99)[1:] if 'fatal' not in x.dcd]):
|
|
self.text.insert('end', self._format(metar.dcd))
|
|
if showfc:
|
|
fcat = AvnLib.flightCategory(metar.dcd)
|
|
self.text.tag_add(fcat, float(n+2), float(n+3))
|
|
# First line of most recent METAR
|
|
return 1
|
|
|
|
def __showRaw(self):
|
|
htw = self.text.component('columnheader')
|
|
htw.configure(state='normal')
|
|
htw.delete('1.0', 'end')
|
|
htw.configure(state='disabled')
|
|
if self._tkShowHeaders.get():
|
|
self.text.insert('end', self._data['header'])
|
|
self.text.insert('end', self._data['text'])
|
|
if 'dcd' in self._data:
|
|
self.__highlightRaw()
|
|
ftime = time.time() - 3600.0 * self._tkNumHours.get()
|
|
for metar in Globals.DRC.getMetars(self._mtrid, False, ftime, 99)[1:]:
|
|
if self._tkShowHeaders.get():
|
|
self.text.insert('end', metar.header)
|
|
self.text.insert('end', metar.text)
|
|
|
|
def __showAll(self):
|
|
htw = self.text.component('columnheader')
|
|
htw.configure(state='normal')
|
|
htw.delete('1.0', 'end')
|
|
htw.configure(state='disabled')
|
|
ftime = time.time() - 3600.0*self._tkNumHours.get()
|
|
last = None
|
|
for metar in Globals.DRC.getMetars(self._ids, False, ftime):
|
|
try:
|
|
id_ = metar.text.split(None, 3)[1]
|
|
if last and last != id_:
|
|
self.text.insert('end', '\n')
|
|
last = id_
|
|
except IndexError:
|
|
pass
|
|
if self._tkShowHeaders.get():
|
|
self.text.insert('end', metar.header)
|
|
self.text.insert('end', metar.text)
|
|
self.text.configure(text_state='disabled')
|
|
|
|
###############################################################################
|
|
# public methods
|
|
###############################################################################
|
|
def load(self, arg=None):
|
|
self._tafviewer.load(self._id)
|
|
try:
|
|
self.text.configure(text_state='normal')
|
|
self.text.clear()
|
|
if self._tkAll.get():
|
|
self.__showAll()
|
|
else:
|
|
self._data = self._getcmd(self._id)
|
|
if self._tkShowDecoded.get():
|
|
self.__showDecoded()
|
|
else:
|
|
self.__showRaw()
|
|
self._tafviewer.highlight(self._data)
|
|
except Exception:
|
|
msg = 'Cannot load data for %s' % self._mtrid
|
|
_Logger.exception(msg)
|
|
if self._master.winfo_ismapped():
|
|
self.messagebar().message('usererror', msg)
|
|
self.text.configure(text_state='disabled')
|
|
|
|
def setAll(self, arg=None):
|
|
if self._tkAll.get():
|
|
self._tkNumHoursHold.set(self._tkNumHours.get())
|
|
self._tkNumHours.set(self._tkNumHoursAll.get())
|
|
else:
|
|
self._tkNumHoursAll.set(self._tkNumHours.get())
|
|
self._tkNumHours.set(self._tkNumHoursHold.get())
|
|
self.load(arg)
|
|
|
|
def setSite(self, id_, ids):
|
|
if self._id == id_:
|
|
return
|
|
self._id = id_
|
|
self._mtrid = None
|
|
self._ids = []
|
|
for i in ids:
|
|
siteinfo = AvnParser.getTafSiteCfg(i)
|
|
if siteinfo and siteinfo['sites'].get('metar'):
|
|
self._ids.extend(siteinfo['sites']['metar'])
|
|
if i == self._id:
|
|
self._mtrid = siteinfo['sites']['metar']
|