Change-Id: If95cb839ad81ca2a842ff7f6926847ac3928d8f2 Former-commit-id: 77e1a4d8f5237e5fae930c1e00589c752f8b3738
822 lines
30 KiB
Python
822 lines
30 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:
|
|
# CigVisTrend.py
|
|
# GFS1-NHD:A9059.0000-SCRIPT;16
|
|
#
|
|
# Status:
|
|
# DELIVERED
|
|
#
|
|
# History:
|
|
# Revision 17
|
|
# Created: 10-AUG-2012 15:00:00 GZHANG
|
|
# DR 14702: Added fix for PyTables in Gui().trend()
|
|
#
|
|
# Revision 16 (DELIVERED)
|
|
# Created: 06-MAR-2008 17:01:20 OBERFIEL
|
|
# Added fix for leap-years.
|
|
#
|
|
# Revision 15 (DELIVERED)
|
|
# Created: 20-MAR-2007 13:36:53 OBERFIEL
|
|
# Updated text input fields and printer dialogs to be
|
|
# consistent. Frame tag removed.
|
|
#
|
|
# Revision 14 (DELIVERED)
|
|
# Created: 04-JAN-2007 12:52:14 OBERFIEL
|
|
# Corrected misspelling.
|
|
#
|
|
# Revision 13 (REVIEW)
|
|
# Created: 28-DEC-2006 11:33:09 OBERFIEL
|
|
# Made validate function less restrictive.
|
|
#
|
|
# Revision 12 (DELIVERED)
|
|
# Created: 30-JUN-2006 10:58:42 TROJAN
|
|
# spr 7190: added plot area window parameters to
|
|
# app-recources file
|
|
#
|
|
# Revision 11 (DELIVERED)
|
|
# Created: 30-MAY-2006 15:10:39 TROJAN
|
|
# spr 7144: added auto-update feature, number of years in
|
|
# database
|
|
#
|
|
# Revision 10 (DELIVERED)
|
|
# Created: 23-MAY-2006 08:47:13 TROJAN
|
|
# spr 7152: fixed select days matching criteria - round time,
|
|
# added history button in TWEB Editor's statusbar,
|
|
# fixed spelling
|
|
#
|
|
# Revision 9 (DELIVERED)
|
|
# Created: 19-MAY-2006 08:32:07 TROJAN
|
|
# SPR 7146: fixed select days matching criteria - round
|
|
# observation time up
|
|
#
|
|
# Revision 8 (DELIVERED)
|
|
# Created: 02-MAY-2006 09:34:10 TROJAN
|
|
# SPR 7133: fixed "where" selection expression for dates
|
|
# wrapping around end of the year
|
|
#
|
|
# Revision 7 (DELIVERED)
|
|
# Created: 02-MAY-2006 08:52:49 TROJAN
|
|
# SPR 7138: fixed "where" selection expression for dates
|
|
# wrapping around end of the year
|
|
#
|
|
# Revision 6 (DELIVERED)
|
|
# Created: 27-MAR-2006 08:11:49 TROJAN
|
|
# spr 7103: added ErrorRedirect method
|
|
#
|
|
# Revision 5 (DELIVERED)
|
|
# Created: 24-MAR-2006 17:45:44 TROJAN
|
|
# spr 7106 Pmw code does not forward python exceptions to
|
|
# AvnFPS _Logger
|
|
#
|
|
# Revision 4 (DELIVERED)
|
|
# Created: 23-FEB-2006 08:24:45 TROJAN
|
|
# moved paths to text database commands to avnenv.sh
|
|
#
|
|
# Revision 3 (DELIVERED)
|
|
# Created: 16-FEB-2006 14:20:58 TROJAN
|
|
# removed unnecessary imports, renamed shadowed variables
|
|
#
|
|
# Revision 2 (APPROVED)
|
|
# Created: 15-FEB-2006 14:34:43 TROJAN
|
|
# fixes transient dialog behavior - spr 7091
|
|
#
|
|
# Revision 1 (APPROVED)
|
|
# Created: 13-FEB-2006 10:25:14 TROJAN
|
|
# stdr 945
|
|
#
|
|
# Change Document History:
|
|
# 1:
|
|
# Change Document: GFS1-NHD_SPR_7373
|
|
# Action Date: 02-JUN-2008 20:44:52
|
|
# Relationship Type: In Response to
|
|
# Status: CLOSED
|
|
# Title: AvnFPS: AvnWatch monitoring not reliable.
|
|
#
|
|
#
|
|
import logging, os, sys, time
|
|
import numpy
|
|
import Avn, ClimLib, MetarDecoder, ClimateProcessLogger
|
|
# for profiling
|
|
#import hotshot, hotshot.stats
|
|
|
|
_Help = {
|
|
'title': 'AvnFPS - Ceiling/Visibility Trend Help', \
|
|
'content': """
|
|
This application displays ceiling/visibility trend based
|
|
on selected initial conditions.
|
|
|
|
Use "Get" button to retrieve METAR for a selected site.
|
|
The METAR can be modified.
|
|
Use "Decode" button to initialize selection widgets.
|
|
The initial conditions can be adjusted either by typing
|
|
in the "value" and "range" windows, or by mouse actions.
|
|
Left button moves value or an edge of range (red area on
|
|
the element widget). Middle button is used to move both
|
|
value and range. In the "Wind Direction" widget use right
|
|
button to toggle between wind arrow and a circle representing
|
|
calm and variable wind.
|
|
Use "Element" radiobuttons to select forecasted element.
|
|
Press "Draw" to display the forecast.
|
|
|
|
The displayed image can be printed or stored in a graphic file.
|
|
Use "File" menu for that purpose.
|
|
|
|
"""
|
|
}
|
|
|
|
import sys
|
|
sys.argv = [__name__]
|
|
|
|
_tmp = range(len(ClimLib.FlightCats))
|
|
CigCat = list(enumerate([ClimLib.FlightCats[_x]['cig'] for _x in _tmp]))
|
|
VisCat = list(enumerate([ClimLib.FlightCats[_x]['vis'] for _x in _tmp]))
|
|
NumCat = len(ClimLib.FlightCats)
|
|
Fill = 9
|
|
|
|
## used by Selection class
|
|
# some documentation
|
|
CatDict = { \
|
|
'dd': [30, 90, 150, 210, 270, 330], \
|
|
'ff': [5, 12, 32], \
|
|
'vsby': [0.4, 1.1, 3.1, 6.1], \
|
|
'cig': [210, 610, 1020, 3130, 5050], \
|
|
}
|
|
|
|
fields = {}
|
|
_Logger = logging.getLogger(ClimateProcessLogger.CLIMATE_CATEGORY)
|
|
# does not seem to work
|
|
#na.Error.setMode(dividebyzero='ignore', invalid='ignore')
|
|
|
|
##############################################################################
|
|
def ff_range(value):
|
|
seq = CatDict['ff']
|
|
seq.insert(0, 0)
|
|
seq.append(sys.maxint)
|
|
for lo, hi in Avn.window(seq):
|
|
if lo <= value < hi:
|
|
break
|
|
elif hi <= value < lo:
|
|
break
|
|
else:
|
|
raise ValueError('Bad ff_range')
|
|
return lo, hi
|
|
|
|
def vsby_range(value):
|
|
seq = CatDict['vsby']
|
|
seq.insert(0, 0)
|
|
seq.append(sys.maxint)
|
|
for lo, hi in Avn.window(seq):
|
|
if lo <= value < hi:
|
|
break
|
|
elif hi <= value < lo:
|
|
break
|
|
else:
|
|
raise ValueError('Bad vsby_range')
|
|
return lo, hi
|
|
|
|
def cig_range(value):
|
|
seq = CatDict['cig']
|
|
seq.insert(0, 0)
|
|
seq.append(sys.maxint)
|
|
for lo, hi in Avn.window(seq):
|
|
if lo <= value < hi:
|
|
break
|
|
elif hi <= value < lo:
|
|
break
|
|
else:
|
|
raise ValueError('Bad cig_range')
|
|
return
|
|
return lo, hi
|
|
|
|
def _cigCat(v):
|
|
for n, cig in CigCat:
|
|
if v < cig:
|
|
return n
|
|
else:
|
|
return NumCat
|
|
|
|
def _visCat(v):
|
|
for n, vis in VisCat:
|
|
if v < vis:
|
|
return n
|
|
else:
|
|
return NumCat
|
|
|
|
def squeeze(matches, tref):
|
|
# eliminate multiple events within the same time window
|
|
def _num(t):
|
|
return (t-tref)//86400.0
|
|
d = dict([(_num(t), (n, t)) for n, t in matches])
|
|
tmp = d.values()
|
|
tmp.sort()
|
|
return tmp
|
|
|
|
def process_data_all(count, data, ref_t, num_hours, table):
|
|
for obs in data:
|
|
dh = int((obs[0]-ref_t)//3600.0)
|
|
if not (0 <= dh < num_hours):
|
|
raise Avn.AvnError('Bug in process_data_all()')
|
|
if obs[1] != table.attrs.cig['fill'] and \
|
|
obs[2] != table.attrs.vis['fill']:
|
|
c = _cigCat(obs[1])
|
|
v = _visCat(obs[2])
|
|
j = min(c, v)
|
|
if c < NumCat:
|
|
count['cig'][dh,c] += 1
|
|
if v < NumCat:
|
|
count['vis'][dh,c] += 1
|
|
if j < NumCat:
|
|
count['joint'][dh,c] += 1
|
|
count['total'][dh] += 1
|
|
|
|
def process_data_onhour(count, data, ref_t, num_hours, table):
|
|
tmp_cig = numpy.ones((num_hours,))*Fill
|
|
tmp_vis = numpy.ones((num_hours,))*Fill
|
|
tmp_joint = numpy.ones((num_hours,))*Fill
|
|
for obs in data:
|
|
dh = int((obs[0]-ref_t)//3600.0)
|
|
if not (0 <= dh < num_hours):
|
|
raise Avn.AvnError('Bug in process_data_onhour()')
|
|
if obs[1] != table.attrs.cig['fill'] and \
|
|
obs[2] != table.attrs.vis['fill']:
|
|
if tmp_joint[dh] == Fill:
|
|
tmp_cig[dh] = _cigCat(obs[1])
|
|
tmp_vis[dh] = _visCat(obs[2])
|
|
tmp_joint[dh] = min(tmp_cig[dh], tmp_vis[dh])
|
|
for h in range(num_hours):
|
|
if tmp_cig[h] < NumCat:
|
|
count['cig'][h,tmp_cig[h]] += 1
|
|
if tmp_vis[h] < NumCat:
|
|
count['vis'][h,tmp_vis[h]] += 1
|
|
if tmp_joint[h] < NumCat:
|
|
count['joint'][h,tmp_joint[h]] += 1
|
|
if tmp_joint[h] < Fill:
|
|
count['total'][h] += 1
|
|
|
|
def process_data_worst(count, data, ref_t, num_hours, table):
|
|
tmp_cig = numpy.ones((num_hours,))*Fill
|
|
tmp_vis = numpy.ones((num_hours,))*Fill
|
|
tmp_joint = numpy.ones((num_hours,))*Fill
|
|
for obs in data:
|
|
dh = int((obs[0]-ref_t)//3600.0)
|
|
if not (0 <= dh < num_hours):
|
|
raise Avn.AvnError('Bug in process_data_worst()')
|
|
if obs[1] != table.attrs.cig['fill'] and \
|
|
obs[2] != table.attrs.vis['fill']:
|
|
tmp_cig[dh] = min(tmp_cig[dh],_cigCat(obs[1]))
|
|
tmp_vis[dh] = min(tmp_vis[dh],_visCat(obs[2]))
|
|
tmp_joint[dh] = min(tmp_cig[dh], tmp_vis[dh])
|
|
for h in range(num_hours):
|
|
if tmp_cig[h] < NumCat:
|
|
count['cig'][h,tmp_cig[h]] += 1
|
|
if tmp_vis[h] < NumCat:
|
|
count['vis'][h,tmp_vis[h]] += 1
|
|
if tmp_joint[h] < NumCat:
|
|
count['joint'][h,tmp_joint[h]] += 1
|
|
if tmp_joint[h] < Fill:
|
|
count['total'][h] += 1
|
|
|
|
##############################################################################
|
|
class Gui():
|
|
AppName = 'AvnFPS - Ceiling/Visibility Trend'
|
|
Xresfile = os.path.join('etc', 'app-resources', 'XCigVisTrend')
|
|
IdsFile = os.path.join('etc', 'ids.cfg')
|
|
RowLabels = ['MVFR', 'IFR', 'LIFR', 'VLIFR', 'COUNT']
|
|
MaxHours = 13 # hour_menu must not contain value >= MaxHours
|
|
def __init__(self):
|
|
pass
|
|
|
|
def get_option_db(self):
|
|
# set default X resources. Read resource file, if available
|
|
self.root.option_add('*font', 'fixed')
|
|
self.root.option_add('*background', 'grey')
|
|
self.root.option_add('*foreground', 'black')
|
|
self.root.option_add('*wrapLength', '0')
|
|
self.root.option_add('*MessageBar.Entry.background', 'grey85')
|
|
self.root.option_add('*wrapLength', '0')
|
|
self.root.option_add('*EntryField.Entry.background', 'white')
|
|
self.root.option_add('*Listbox*background', 'white')
|
|
self.root.option_add('*plotWidth', '6.0')
|
|
self.root.option_add('*plotHeight', '3.5')
|
|
self.root.option_add('*dpi', '90')
|
|
self.root.option_add('*vlifr', 'purple')
|
|
self.root.option_add('*lifr', 'red')
|
|
self.root.option_add('*ifr', 'yellow')
|
|
self.root.option_add('*mvfr', 'blue')
|
|
self.root.option_add('*plotX0', '0.12')
|
|
self.root.option_add('*plotY0', '0.30')
|
|
self.root.option_add('*plotX1', '0.98')
|
|
self.root.option_add('*plotY1', '0.90')
|
|
if os.path.isfile(self.Xresfile):
|
|
self.root.option_readfile(self.Xresfile)
|
|
vlifr_color = self.root.option_get('vlifr', '')
|
|
lifr_color = self.root.option_get('lifr', '')
|
|
ifr_color = self.root.option_get('ifr', '')
|
|
mvfr_color = self.root.option_get('mvfr', '')
|
|
# white for COUNT
|
|
self.colors = [mvfr_color, ifr_color, lifr_color, vlifr_color, 'white']
|
|
Pmw.Color.setscheme(self.root,
|
|
background=self.root.option_get('background', ''))
|
|
|
|
def get_site_config(self):
|
|
# get list of data files
|
|
cp = ConfigParser.SafeConfigParser()
|
|
cp.read(self.IdsFile)
|
|
self.ids = dict([(x, dict(cp.items(x))) for x in cp.sections()])
|
|
if self.ids:
|
|
tmp = self.ids.keys()
|
|
tmp.sort()
|
|
self.station_w.setlist(tmp)
|
|
self.station_w.selection_set(0)
|
|
|
|
def set_geometry(self, widget):
|
|
"""forces dialog 30 pixels off parent's left top position"""
|
|
x, y = self.interior().winfo_rootx(), self.interior().winfo_rooty()
|
|
s_w, s_h = self.interior().winfo_screenwidth(), \
|
|
self.interior().winfo_screenheight()
|
|
w_w, w_h = widget.winfo_reqwidth(), widget.winfo_reqheight()
|
|
x, y = min(x+30, s_w-w_w), min(y+30, s_h-w_h)
|
|
widget.wm_geometry('+%d+%d' % (x, y))
|
|
|
|
def save_image(self):
|
|
# save image to a file
|
|
opts = {'initialdir': 'tmp', 'filetypes': [('PostScript', '.ps'), \
|
|
('JPEG', '.jpg'), ('PNG', '.png'), ('all', '*')]}
|
|
filename = Busy.asksaveasfilename(self.interior(), **opts)
|
|
if not filename:
|
|
return
|
|
if '.' not in filename:
|
|
msg = '%s does not have an extension, defaulting to Postscript' % \
|
|
filename
|
|
if Busy.askokcancel(msg, self.interior()):
|
|
self.canvas.get_tk_widget().postscript(file=filename,
|
|
colormode='color')
|
|
elif filename.endswith('.ps'):
|
|
self.canvas.get_tk_widget().postscript(file=filename,
|
|
colormode='color')
|
|
else:
|
|
tmpfile = os.tmpnam()+'.ps'
|
|
self.canvas.get_tk_widget().postscript(file=tmpfile,
|
|
colormode='color')
|
|
command = 'convert %s %s' % (tmpfile, filename)
|
|
chldin, chldout = os.popen4(command, -1)
|
|
chldin.close()
|
|
msg = chldout.read()
|
|
if msg:
|
|
Busy.showinfo(msg, self.interior())
|
|
os.system('convert %s %s' % (tmpfile, filename))
|
|
os.unlink(tmpfile)
|
|
|
|
def create_print_dialog(self):
|
|
# create print dialog
|
|
self.print_dialog = Pmw.Dialog(self.interior(),
|
|
buttons=('OK', 'Cancel'),
|
|
defaultbutton='OK',
|
|
title='Print',
|
|
command=self.print_,
|
|
)
|
|
self.print_dialog.withdraw()
|
|
|
|
interior = self.print_dialog.interior()
|
|
|
|
self.print_dialog.mode = Pmw.RadioSelect(interior,
|
|
buttontype = 'radiobutton',
|
|
labelpos='w',
|
|
label_text='Palette',
|
|
orient='horizontal',
|
|
hull_borderwidth=2,
|
|
hull_relief='ridge',
|
|
)
|
|
self.print_dialog.mode.pack(side='top', fill='x', expand='yes', padx=5)
|
|
for btn in ['gray', 'color']:
|
|
self.print_dialog.mode.add(btn)
|
|
self.print_dialog.mode.setvalue('gray')
|
|
self.print_dialog.command = Pmw.EntryField(interior,
|
|
labelpos='w',
|
|
label_text='command',
|
|
entry_width=12,
|
|
validate={'min': 1,'max': 50, 'minstrict':0},
|
|
value='lpr',
|
|
)
|
|
self.print_dialog.command.pack(side='top', fill='x', expand='yes',
|
|
padx=5)
|
|
|
|
def show_help_dialog(self):
|
|
dialog = HelpDialog()
|
|
if dialog.winfo_ismapped():
|
|
return
|
|
self.set_geometry(dialog)
|
|
dialog.display(_Help)
|
|
|
|
def show_print_dialog(self):
|
|
if self.print_dialog.winfo_ismapped():
|
|
return
|
|
self.set_geometry(self.print_dialog)
|
|
self.print_dialog.transient(self.interior())
|
|
self.print_dialog.show()
|
|
|
|
def print_(self, action):
|
|
# print image
|
|
if action == 'OK' and self.print_dialog.command.valid():
|
|
command = self.print_dialog.command.getvalue()
|
|
mode = self.print_dialog.mode.getvalue()
|
|
#
|
|
# If color palette selected, then make sure the -P flag is present
|
|
if mode == 'color':
|
|
lpdest = re.compile('-P\s+\w+')
|
|
if not lpdest.search(command):
|
|
cmd = command.split(' ')
|
|
cmd.insert(1,'lp2')
|
|
cmd.insert(1,'-P')
|
|
command = ' '.join(cmd)
|
|
|
|
chldin, chldout = os.popen4(command, -1)
|
|
chldin.write(self.canvas.get_tk_widget().postscript(
|
|
colormode=mode))
|
|
chldin.close()
|
|
msg = chldout.read()
|
|
if msg:
|
|
Busy.showinfo(msg, self.interior())
|
|
self.print_dialog.withdraw()
|
|
self.print_dialog.deactivate()
|
|
|
|
def get_metar(self, siteID):
|
|
import JUtil
|
|
import MetarData
|
|
data = MetarData.retrieve([siteID], 1)
|
|
if data is None:
|
|
raise Avn.AvnError('Cannot retrieve data for %s' % siteID)
|
|
data = [{'header' : d.header, 'text' : d.text, 'dcd' : d.dcd} for d in data]
|
|
data.sort(lambda x, y: cmp(y['dcd']['itime']['str'], x['dcd']['itime']['str']))
|
|
tms = time.gmtime(data[0]['dcd']['itime']['value'])
|
|
return JUtil.pyValToJavaObj(data[0])
|
|
|
|
def decode_metar(self):
|
|
metar = self.metar_w.getvalue().upper().split('\n')
|
|
if not metar:
|
|
return
|
|
for n, line in enumerate(metar):
|
|
if line.startswith('METAR') or line.startswith('SPECI'):
|
|
metar = ''.join(metar[n:])
|
|
break
|
|
else:
|
|
msg = 'Cannot find keywords METAR or SPECI'
|
|
Busy.showerror(msg, self.interior())
|
|
return
|
|
dcd = self.decoder(metar)
|
|
if 'fatal' in dcd:
|
|
msg = 'Cannot decode report:\n', metar
|
|
Busy.showerror(msg, self.interior())
|
|
return
|
|
tms = time.gmtime(dcd['itime']['value'])
|
|
hour = tms.tm_hour+tms.tm_min/60.0
|
|
self.hour_w.draw_range(hour-1.0, hour+1.0)
|
|
self.hour_w.draw_value(hour)
|
|
self.day_w.draw_range(tms.tm_yday-30, tms.tm_yday+30)
|
|
self.day_w.draw_value(tms.tm_yday-1)
|
|
try:
|
|
vsby = dcd['vsby']['value']
|
|
lo, hi = self.vsby_w.get_range()
|
|
if not lo <= vsby < hi:
|
|
lo, hi = vsby_range(vsby)
|
|
self.vsby_w.draw_range(lo, hi)
|
|
self.vsby_w.draw_value(vsby)
|
|
except KeyError:
|
|
self.messagebar.message('usererror', 'Skipping visibility...')
|
|
try:
|
|
cig = dcd['sky']['cig']
|
|
lo, hi = self.cig_w.get_range()
|
|
if not lo <= cig < hi:
|
|
lo, hi = cig_range(cig)
|
|
self.cig_w.draw_range(lo, hi)
|
|
self.cig_w.draw_value(cig)
|
|
except KeyError:
|
|
self.messagebar.message('usererror', 'Skipping ceiling...')
|
|
try:
|
|
dd = dcd['wind']['dd']
|
|
if dd == 0 or dd == 'VRB':
|
|
dd = 'CALM'
|
|
lo, hi = self.wind_dir_w.get_range()
|
|
if dd == 'CALM':
|
|
self.wind_dir_w.draw_range(1, 360)
|
|
else:
|
|
self.wind_dir_w.draw_range(dd-30, dd+30)
|
|
self.wind_dir_w.draw_value(dd)
|
|
except KeyError:
|
|
self.messagebar.message('usererror', 'Skipping wind direction...')
|
|
try:
|
|
ff = dcd['wind']['ff']
|
|
lo, hi = self.wind_speed_w.get_range()
|
|
if not lo <= ff < hi:
|
|
lo, hi = ff_range(ff)
|
|
self.wind_speed_w.draw_range(lo, hi)
|
|
self.wind_speed_w.draw_value(ff)
|
|
except KeyError:
|
|
self.messagebar.message('usererror', 'Skipping wind speed...')
|
|
try:
|
|
dcd['pcp']['str']
|
|
self.pcp_w.setvalue('yes')
|
|
except KeyError:
|
|
self.pcp_w.setvalue('no')
|
|
|
|
def trend(self, table, selection, unltd_cig):
|
|
def _in(value, from_, to):
|
|
if from_ <= to:
|
|
return from_ <= value <= to
|
|
else:
|
|
return not (to < value < from_)
|
|
|
|
def time_selector11(sel):
|
|
yday0 = sel.yday[0]
|
|
yday1 = sel.yday[1]
|
|
for o in table.where('(yday0<=yday) & (yday<=yday1)'):
|
|
if sel.hour[0] <= o['hour'] <= sel.hour[1]:
|
|
yield o
|
|
|
|
def time_selector12(sel):
|
|
yday0 = sel.yday[0]
|
|
yday1 = sel.yday[1]
|
|
for o in table.where('(yday0<=yday) & (yday<=yday1)'):
|
|
if o['hour'] <= sel.hour[1] or sel.hour[0] <= o['hour']:
|
|
yield o
|
|
|
|
def time_selector21(sel):
|
|
yday0 = sel.yday[0]
|
|
yday1 = sel.yday[1]
|
|
for o in table.where('yday0<=yday'):
|
|
if sel.hour[0] <= o['hour'] <= sel.hour[1]:
|
|
yield o
|
|
for o in table.where('yday<=yday1'):
|
|
if sel.hour[0] <= o['hour'] <= sel.hour[1]:
|
|
yield o
|
|
|
|
def time_selector22(sel):
|
|
yday0 = sel.yday[0]
|
|
yday1 = sel.yday[1]
|
|
for o in table.where('yday0<=yday'):
|
|
if o['hour'] <= sel.hour[1] or sel.hour[0] <= o['hour']:
|
|
yield o
|
|
for o in table.where('yday<=yday1'):
|
|
if o['hour'] <= sel.hour[1] or sel.hour[0] <= o['hour']:
|
|
yield o
|
|
|
|
def wx_selector(row, sel):
|
|
ff = row['wind_spd']
|
|
if ff == table.attrs.wind_spd['fill']:
|
|
return False
|
|
if not _in(ff, *sel.wind_speed):
|
|
return False
|
|
dd = row['wind_dir']
|
|
if dd == table.attrs.wind_dir['fill']:
|
|
if ff == 0:
|
|
dd = 0
|
|
else:
|
|
typ = row['wdir_type']
|
|
if typ == 'V':
|
|
dd = 0
|
|
else:
|
|
return False
|
|
else:
|
|
dd = float(dd)
|
|
if dd > 0:
|
|
if not _in(dd, *sel.wind_dir):
|
|
return False
|
|
cig = row['cig']
|
|
if cig == table.attrs.cig['fill']:
|
|
return False
|
|
if not _in(cig, *sel.cig):
|
|
return False
|
|
vsby = row['vis']
|
|
if vsby == table.attrs.vis['fill']:
|
|
return False
|
|
if not _in(vsby, *sel.vsby):
|
|
return False
|
|
pcp = [x for x in row['pres_wxm_code'][:] if 50 <= x < 100] != []
|
|
if pcp and sel.pcp == 'no' or not pcp and sel.pcp == 'yes':
|
|
return False
|
|
return True
|
|
|
|
# convert units
|
|
sel = Avn.Bunch(wind_dir=selection.wind_dir,
|
|
wind_speed=[ClimLib.us2hd_wind_speed(x) \
|
|
for x in selection.wind_speed],
|
|
cig=[ClimLib.us2hd_cig(x, unltd_cig) for x in selection.cig],
|
|
vsby=[ClimLib.us2hd_vsby(x) for x in selection.vsby],
|
|
hour=selection.hour,
|
|
yday=selection.yday,
|
|
pcp=selection.pcp,
|
|
cur_hour=selection.cur_hour,
|
|
)
|
|
# fix hour selection - round to full hour
|
|
def _round(h):
|
|
return (h+1.0/6.0)//1.0 # 10 min before hour
|
|
sel.cur_hour = _round(sel.cur_hour)
|
|
sel.hour = [_round(x) for x in sel.hour]
|
|
|
|
# create search selector
|
|
if sel.yday[0] <= sel.yday[1]:
|
|
if sel.hour[0] <= sel.hour[1]:
|
|
time_selector = time_selector11
|
|
else:
|
|
time_selector = time_selector12
|
|
else:
|
|
if sel.hour[0] <= sel.hour[1]:
|
|
time_selector = time_selector21
|
|
else:
|
|
time_selector = time_selector22
|
|
|
|
# select days matching criteria
|
|
matches = [(row.nrow, row['date_time']+600) for row in \
|
|
time_selector(sel) if wx_selector(row, sel)]
|
|
tref = 3600.0*sel.hour[0]
|
|
count = {'cig': numpy.zeros((self.MaxHours, NumCat), numpy.float32), \
|
|
'vis': numpy.zeros((self.MaxHours, NumCat), numpy.float32), \
|
|
'joint': numpy.zeros((self.MaxHours, NumCat), numpy.float32), \
|
|
'total': numpy.zeros((self.MaxHours,))
|
|
}
|
|
delta = (sel.cur_hour-sel.hour[0])%24.0*3600.0 + tref
|
|
# stats method
|
|
process_data = process_data_worst
|
|
for n, t in squeeze(matches, tref):
|
|
t0 = (t-tref)//86400.0*86400.0 + delta # time corrresponding to
|
|
# the selected hour
|
|
t1 = t0-600.0 # start search time
|
|
t2 = t1 + self.MaxHours*3600.0 # end search time
|
|
# print '==========', time.ctime(t), time.ctime(t1), time.ctime(t2)
|
|
n1 = max(0, n-2*int((t-t1)//3600.0)-1) # small enough
|
|
n2 = n1 + 5*self.MaxHours # big enough
|
|
data = [(row['date_time'], row['cig'], row['vis']) for row in \
|
|
table.where('(t1<=date_time) & (date_time<t2)', start=n1, stop=n2)]
|
|
process_data(count, data, t1, self.MaxHours, table)
|
|
# calculate frequencies
|
|
args = count.keys()
|
|
args.remove('total')
|
|
tmp = 0.01*count['total']
|
|
for arg in args:
|
|
for n in range(NumCat):
|
|
count[arg][:,n] /= tmp
|
|
return count
|
|
|
|
def draw(self, event=None):
|
|
if self.data is None:
|
|
return
|
|
self.ax.cla()
|
|
elem = self.element_w.getvalue()
|
|
item = {'vis': 'visibility', 'cig': 'ceiling', \
|
|
'joint': 'flight category'}.get(elem, '')
|
|
title = '%s %02dZ %s forecast' % (self.id_, self.data.hour, item)
|
|
self.ax.set_title(title)
|
|
num_hours = int(self.hour_menu.getvalue())+1
|
|
col_labels = ['%02dZ' % ((x+self.data.hour)%24) \
|
|
for x in range(num_hours)]
|
|
xvals = na.arange(num_hours) + 0.2
|
|
yoff = na.zeros(len(col_labels), na.Float32)
|
|
bar = [None]*NumCat
|
|
widths = [0.6]*num_hours
|
|
self.ax.xaxis.set_major_locator(multipleLocator)
|
|
cell_text = []
|
|
for row in range(NumCat):
|
|
yvals = self.data.count[elem][:,row][:num_hours]
|
|
bar[row] = self.ax.bar(xvals, yvals, widths, bottom=yoff,
|
|
color=self.colors[NumCat-row-1])
|
|
yoff += yvals
|
|
cell_row = ['']*num_hours
|
|
for n in range(num_hours):
|
|
if self.data.count['total'][n] > 0:
|
|
cell_row[n] = '%.0f' % yvals[n]
|
|
cell_text.append(cell_row)
|
|
cell_text.reverse()
|
|
cell_row = [str(self.data.count['total'][n]) for n in range(num_hours)]
|
|
cell_text.append(cell_row)
|
|
self.ax.table(cellText=cell_text, rowLabels=self.RowLabels,
|
|
rowColours=self.colors, colLabels=col_labels, loc='bottom')
|
|
# axes
|
|
self.ax.set_ylabel('Percent occurrence')
|
|
self.ax.set_xticks([])
|
|
ymax, delta = 105, 10
|
|
self.ax.set_yticks(na.arange(0, ymax, delta))
|
|
self.ax.grid(True)
|
|
self.canvas.draw()
|
|
|
|
def wait_for_data(self):
|
|
try:
|
|
i, (id_, data) = self.resultqueue.get_nowait()
|
|
except Queue.Empty:
|
|
self.after(100, self.wait_for_data)
|
|
return
|
|
if type(data) == type(''):
|
|
self.messagebar.message('systemerror', data)
|
|
self.data = None
|
|
else:
|
|
self.data = data
|
|
self.id_ = id_
|
|
Busy.Manager.notbusy()
|
|
self.draw()
|
|
|
|
def display(self):
|
|
try:
|
|
id_ = self.station_w.getcurselection()[0]
|
|
selection = Avn.Bunch(cig=self.cig_w.get_range(),
|
|
vsby=self.vsby_w.get_range(),
|
|
wind_speed=self.wind_speed_w.get_range(),
|
|
wind_dir=self.wind_dir_w.get_range(),
|
|
hour=self.hour_w.get_range(),
|
|
yday=self.day_w.get_range(),
|
|
pcp=self.pcp_w.getvalue(),
|
|
cur_hour=self.hour_w.get_value(),
|
|
)
|
|
except IndexError:
|
|
Busy.showerror('Select site from list', self.interior())
|
|
return
|
|
except ValueError, e:
|
|
Busy.showerror(str(e), self.interior())
|
|
return
|
|
msg = 'Retrieving data for %s, this will take a while' % id_
|
|
self.messagebar.message('userevent', msg)
|
|
Busy.Manager.busy()
|
|
self.update_idletasks()
|
|
self.worker.performWork(self.get_data, selection, id_)
|
|
self.wait_for_data()
|
|
|
|
def get_data(self, selectionDict, id_, hours, fname, queue):
|
|
self.MaxHours = hours
|
|
selection = Avn.Bunch(cig=selectionDict['cig'],
|
|
vsby=selectionDict['vsby'],
|
|
wind_speed=selectionDict['wind_speed'],
|
|
wind_dir=selectionDict['wind_dir'],
|
|
hour=selectionDict['hour'],
|
|
yday=selectionDict['yday'],
|
|
pcp=selectionDict['pcp'],
|
|
cur_hour=selectionDict['cur_hour'],
|
|
)
|
|
|
|
try:
|
|
if not os.path.isfile(fname):
|
|
raise Avn.AvnError('File %s does not exist' % fname)
|
|
import tables
|
|
fh = tables.openFile(fname, 'r')
|
|
try:
|
|
import warnings
|
|
# following function - table = fh.getNode('/obs') throws extraneous warnings
|
|
# so going to filter warnings on this function
|
|
warnings.simplefilter("ignore")
|
|
table = fh.getNode('/obs')
|
|
warnings.simplefilter("default")
|
|
unltd = ClimLib.Unlimited
|
|
data = self.trend(table, selection, unltd)
|
|
cig_count = data['cig']
|
|
vis_count = data['vis']
|
|
joint_count = data['joint']
|
|
total_count = data['total']
|
|
dataDict = {}
|
|
cigList = []
|
|
visList = []
|
|
jntList = []
|
|
totalList = []
|
|
for h in range(self.MaxHours):
|
|
totalList.append(float(total_count[h]))
|
|
tmpCig = []
|
|
tmpVis = []
|
|
tmpJnt = []
|
|
for c in range(NumCat):
|
|
tmpCig.append(float(cig_count[h][c]))
|
|
tmpVis.append(float(vis_count[h][c]))
|
|
tmpJnt.append(float(joint_count[h][c]))
|
|
cigList.append(tmpCig)
|
|
visList.append(tmpVis)
|
|
jntList.append(tmpJnt)
|
|
dataDict['cig'] = cigList
|
|
dataDict['vis'] = visList
|
|
dataDict['joint'] = jntList
|
|
dataDict['total'] = totalList
|
|
queue.put(dataDict)
|
|
queue.put("done")
|
|
finally:
|
|
fh.close()
|
|
return data
|
|
except Exception, e:
|
|
msg = 'Cannot retrieve data for %s: %s' % (id_, e)
|
|
_Logger.error(msg)
|
|
print msg
|
|
|
|
##############################################################################
|
|
|