awips2/cave/com.raytheon.viz.avnconfig/localization/aviation/python/ClimateDataDialog.py
Max Schenkelberg 6f60751ec6 Issue #2033 moved avnfps and text workstation files into respective plugins.
Change-Id: If95cb839ad81ca2a842ff7f6926847ac3928d8f2

Former-commit-id: 77e1a4d8f5237e5fae930c1e00589c752f8b3738
2013-08-15 12:21:43 -05:00

638 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:
# %PM%
# %PID%
#
# Status:
# %PS%
#
# History:
# %PL%
#
# Change Document History:
# %PIRC%
#
import logging, os, time, stat, types, sys, trace, atexit, signal, re
import ConfigParser
from Tkinter import *
import Pmw
import tkFileDialog
from Balloon import Balloon
import Avn, AppShell, AvnDialog, Busy, ErrorRedirect, ConfigParser
import ftplib, gzip
import ClimateDataUtils as cdutils
import ClimateDataUpdate as cdupdate
import threading
from HelpDialog import HelpDialog
Python = os.environ.get('PYTHON','/awips/adapt/avnfps/Python-2.4.1/bin/python')
TopDir = os.environ.get('TOP_DIR','/awips/adapt/avnfps/')
_Help = {
'title': 'Climate Data Update Dialog Help',
'content': """This dialog is used to create and append climatology data files.
Menu Bar
File:
Quit: Close the dialog immediately
Commands:
Show observations history: Opens a graphical display showing the current
inventory of climate data available.
Update NCDC "ish" files: Opens a dialog allowing the user to generate
automated scripts for downloading the Integrated
Surface Hourly (ISH) Database.
Tools:
Pulldown menu consist of the AvnFPS Climate Tools that read the HDF5 climate
files.
Options:
Append: Add new data to existing climate data file
Create: Generate new climate data files, regardless of whether a file for
that site already exists
Fields:
SITE ID: Site ID of the site currently selected, or
a user entered site ID if creating a new data file
METAR AFOS ID: The AFOS ID used to retrieve location's METAR product for
use in AvnFPS's climate Cig/Vis Trend tool.
Idents:
List of current site IDs.
Site info list:
List of IDs and years of data available for each selected site
Monitor area:
Area where all informative messages are displayed.
Buttons:
Assess Data:
After sites are selected, click this to start the creation
or append process
Generate Scripts:
Generate download scripts to retrieve data files from NCDC
Process Data:
Incorporate NCDC data into HDF5 file(s).
Validate Data:
Temporarily move newly changed/created files to a location
so that AvnFPS climate tools can examine the new climate
file.
Commit:
Move newly changed/created files to its permanent location.
Clicking this will also generate new station climate qc
files (files that end in .nc in the data/climate directory)
Reject:
Reject the newly created files in favor of the original file(s),
if available. This action deletes newly created files.
Save Log:
Save all output in the Monitor area to a file
"""
}
# this script will create the qc climate files for
# each station whenever the changed file is 'committed'
# note that this file locates in $TOP_DIR/tmp and
# deletes itself after running
make_qc_script="""#!/bin/csh
foreach site ( %s )
foreach month ( 01 02 03 04 05 06 07 08 09 10 11 12 )
$TOP_DIR/bin/avnstart.sh avnqcstats $site $month
end
end
rm $0
"""
_Logger = logging.getLogger(__name__)
###############################################################################
def _exitfun():
pass
##########################################################################################
#
# class extensions and overrides
#
class myScrolledListBox(Pmw.ScrolledListBox):
def invoke(self):
command = self['selectioncommand']
if callable(command):
command()
class myRadioSelect(Pmw.RadioSelect):
def disabled(self, disabled=ACTIVE):
for x in range(self.numbuttons()):
self.button(x).configure(state=disabled)
##########################################################################################
#
#
class Editor(AppShell.AppShell):
appname = AppShell.AppShell.appname + ' Climate Data Menu'
resizewidth = 'true'
resizeheight = 'true'
IdsFile = os.path.join('etc', 'ids.cfg')
append = True
stns = {}
idnums = {}
oldIdList = []
def __getXResources(self):
fname = os.path.join('etc', 'app-resources', 'X')
if os.path.isfile(fname):
self.option_readfile(fname)
else:
msg = 'Cannot access default resources file,\n' + \
'using build-in values'
_Logger.info(msg)
Busy.showwarning(msg, self.root)
def initializeTk(self):
pass
def appInit(self, **kw):
self.__getXResources()
Pmw.Color.setscheme(self.root,
background=self.option_get('background', ''))
self.root.wm_iconbitmap(Avn.WatchBitmap)
ErrorRedirect.fixlogging(_Logger, self.root)
def appFinish(self):
atexit.register(_exitfun)
def __del__(self):
if hasattr(self, "mythread"):
self.mythread.kill()
time.sleep(.1)
del self.mythread
time.sleep(.1)
def destroy(self):
self.__del__()
def createInterface(self):
frame = Frame(self.interior())
topper = Frame(frame)
action = Frame(frame)
create = Frame(action,
borderwidth=2,
relief='ridge')
self.rbsel = Pmw.RadioSelect(action,
buttontype='radiobutton',
labelpos='e',
hull_borderwidth=2,
hull_relief='ridge',
orient='vertical',
command=self.__setAppend,
)
appendbtn=self.rbsel.add("Append")
Balloon().bind(appendbtn,"Append data to existing HDF5 climate file")
createbtn=self.rbsel.add("Create")
Balloon().bind(createbtn,"Create new HDF5 climate file")
self.rbsel.setvalue("Append")
self.rbsel.grid(row=0, column=0, sticky='news', padx=0, pady=5)
self.site_id = Pmw.EntryField(create,
labelpos='nw',
label_text='SITE ID:',
validate={'validator': 'alphanumeric',
'min': 4, 'max': 4, 'minstrict': 0},
entry_width=10,
)
self.site_id.grid(row=0, column=0, sticky='news', padx=5, pady=0)
self.site_id.component('entry').configure(state=DISABLED)
self.pil = Pmw.EntryField(create,
labelpos='nw',
label_text='METAR AFOS ID:',
validate=self.__validateAFOS,
entry_width=10,
)
self.pil.grid(row=1, column=0, sticky='news', padx=5, pady=0)
self.pil.component('entry').configure(state=DISABLED)
self.create_btn = Button(create, text='Write Site Data', command=self.__create)
Balloon().bind(self.create_btn, 'Add new site to etc/ids.cfg file')
self.create_btn.configure(state=DISABLED)
self.create_btn.grid(row=2, column=0, padx=5, pady=5)
create.grid_rowconfigure(0, weight=1)
create.grid_rowconfigure(1, weight=1)
create.grid_rowconfigure(2, weight=1)
create.grid(row=1, column=0, sticky='news')
action.grid_rowconfigure(0, weight=0)
action.grid_rowconfigure(1, weight=0)
action.grid_rowconfigure(2, weight=0)
action.grid_rowconfigure(3, weight=1)
action.grid(row=0, column=0, rowspan=2, sticky='news', padx=5, pady=5)
self.idents = myScrolledListBox(topper,
label_text='Idents',
labelpos='n',
listbox_width=6,
listbox_height=8,
listbox_exportselection=0,
listbox_selectmode=MULTIPLE,
selectioncommand=self.listIdnums,
)
self.idents.grid(row=0, column=0, rowspan=2)
fixedFont = Pmw.logicalfont('Fixed')
self.st = Pmw.ScrolledText(topper,
columnheader=1,
columnheader_height=36,
usehullsize=1,
hull_height=36,
hull_width=414,
Header_font = fixedFont,
Header_foreground = 'white',
)
self.st.grid(row=0, column=1, sticky='news')
self.sites = Pmw.ScrolledListBox(topper,
listbox_width=41,
listbox_height=6,
hscrollmode='none',
listbox_font=fixedFont,
listbox_exportselection=0,
listbox_selectmode=MULTIPLE,
)
self.sites.grid(row=1, column=1, sticky='news')
header = ' Years in Archive:\n'
header = header + 'Site USAF-WBAN NCDC Local '
self.st.component('columnheader').insert(0.0, header)
self.monitor = Pmw.ScrolledText(
topper,
vscrollmode='static',
hscrollmode='dynamic',
text_height=14,
text_width=50,
text_state='disabled',
text_wrap=WORD,
text_name='monitor')
self.monitor.grid(row=2, column=0, columnspan=2, sticky='news')
self.pct_complete = Pmw.ScrolledText(topper,
text_height = 1,
text_width = 50,
text_background = 'white',
text_foreground = 'blue',
)
self.pct_complete.grid(row=3, column=0, columnspan=2, sticky='news')
topper.grid_rowconfigure(0, weight=0)
topper.grid_rowconfigure(1, weight=1)
topper.grid_rowconfigure(2, weight=1)
topper.grid_rowconfigure(3, weight=1)
topper.grid(row=0, column=1)
frame.grid_rowconfigure(0, weight=0)
frame.grid_rowconfigure(1, weight=1)
frame.pack(side='left', expand='yes', fill='both')
bframe = Frame(frame,
relief=RIDGE,
borderwidth=2,
)
bwidth=14
self.start_btn = Button(bframe, text='Assess Data', command=self.__start, state=DISABLED, width=bwidth)
self.start_btn.pack(padx=5, pady=6)
Balloon().bind(self.start_btn, 'Start or re-start updating data for selected station(s)')
self.scripts_btn = Button(bframe, text='Generate Scripts', command=self.__scripts, state=DISABLED, width=bwidth)
self.scripts_btn.pack(padx=5, pady=6)
Balloon().bind(self.scripts_btn, 'Create scripts to download NCDC data files')
self.continue_btn = Button(bframe, text='Process Data', command=self.__continue, state=DISABLED, width=bwidth)
self.continue_btn.pack(padx=5, pady=6)
Balloon().bind(self.continue_btn, 'Process NCDC data for station(s)')
self.validate_btn = Button(bframe, text='Validate', command=self.__validate, state=DISABLED, width=bwidth)
self.validate_btn.pack(padx=5, pady=6)
Balloon().bind(self.validate_btn, 'Check new HDF5 file(s)')
self.commit_btn = Button(bframe, text='Commit', command=self.__commit, state=DISABLED, width=bwidth)
self.commit_btn.pack(padx=5, pady=6)
Balloon().bind(self.commit_btn, 'Keep new HDF5 climate file(s)')
self.reject_btn = Button(bframe, text='Reject', command=self.__reject, state=DISABLED, width=bwidth)
self.reject_btn.pack(padx=5, pady=6)
Balloon().bind(self.reject_btn, 'Reject new HDF5 file(s) and restore previous')
btn = Button(bframe, text='Save Log', command=self.__saveLog, width=bwidth)
btn.pack(padx=5, pady=6)
Balloon().bind(btn, 'Save a log for this update')
bframe.grid(column=3, row=0, padx=5, pady=5, sticky='nwe')
self.listIdents()
self.initialiseoptions(Editor)
#self.setGeometry()
def createMenuBar(self):
self.menubar.addmenu('Commands','Additional Commands')
#self.menubar.addmenuitem('Commands', 'command',
# label='Resume Previous',
# command=self.__restore,
#)
self.menubar.addmenuitem('Commands', 'command',
#label='Show details of data from selected sites',
label='Show observations history',
command=self.__details
)
self.menubar.addmenuitem('Commands', 'command',
#label='Update history and archive list from NCDC',
label='Update NCDC "ish" Files',
command=self.__helper
)
self.menubar.addmenuitem('Help', 'command',
label='About...',
command=self.showAbout,
)
self.menubar.addmenuitem('Help', 'command',
label='Usage',
command=self.__showHelpDialog,
)
self.menubar.addmenuitem('File', 'command',
label='Quit',
command=self.exit,
)
self.menubar.addmenu('Tools','Examine climate data files' )
self.menubar.addmenuitem('Tools', 'command',
label='METARs',
command=Avn.curry(self.__climate, 'metars'),
)
self.menubar.addmenuitem('Tools', 'command',
label='Wind Rose',
command=Avn.curry(self.__climate, 'wrose'),
)
self.menubar.addmenuitem('Tools', 'command',
label='CigVis Dist',
command=Avn.curry(self.__climate, 'cvdist'),
)
self.menubar.addmenuitem('Tools', 'command',
label='CigVis Trend',
command=Avn.curry(self.__climate, 'cvtrend'),
)
def listIdents(self, stn=''):
self.messagebar().resetmessages('systemerror')
idents = cdutils.getIdentsList()
if idents:
self.idents.setlist(idents)
else:
self.idents.clear()
msg = 'Error reading ids.cfg file'
self.messagebar().message('usererror', msg)
if stn:
idx = list(self.idents.get()).index(stn)
self.idents.yview_scroll(idx, UNITS)
self.idents.selection_set(idx)
self.idents.invoke()
def listIdnums(self):
Busy.Manager.busy(self.interior())
siteid = None
siteids = self.idents.getvalue()
self.__latestId()
for akey in [key for key in self.idnums.keys() if key not in siteids]:
del self.idnums[akey]
for siteid in siteids:
if not self.idnums.has_key(siteid):
self.idnums[siteid] = cdutils.getIdnumsList(siteid)
if self.idnums:
self.start_btn.configure(state=ACTIVE)
numslist = []
idnums_keys = self.idnums.keys()
idnums_keys.sort()
for stn in idnums_keys:
for idnum in self.idnums[stn]:
#numslist.append('%s %s-%s %s-%s %s-%s' % tuple([stn] + idnum))
numslist.append('%-7s%-15s%-12s%-9s' % tuple([stn] + ["%s-%s" % tuple(idnum[:2])] + ["%s-%s" % tuple(idnum[2:4])] + ["%s-%s" % tuple(idnum[4:])]))
if not self.idnums[stn]:
numslist.append('%s -- NO INFO AVAILABLE --' % stn)
deselected_list = [self.sites.get(x) for x in range(self.sites.size()) \
if not self.sites.selection_includes(x)]
self.sites.setlist(numslist)
[self.sites.selection_set(x) for x in range(len(numslist)) if numslist[x] not in deselected_list]
else:
self.start_btn.configure(state=DISABLED)
self.sites.clear()
if siteid != None:
msg = 'Cannot read ID numbers for %s' % siteid
self.messagebar().message('usererror', msg)
if siteids: self.setSiteCreateInfo()
Busy.Manager.notbusy()
def setSiteCreateInfo(self):
if self.append: return # don't do this if we're appending to a site
cp = ConfigParser.SafeConfigParser()
cp.read(self.IdsFile)
try:
if self.newId not in cp.sections(): return
self.site_id.setvalue(self.newId)
self.pil.setvalue(cp.get(self.newId, 'pil'))
self.create_btn.configure(state=DISABLED)
except:
pass
def getdir(self):
return self.__getdir()
def restore(self):
self.__restore()
def start(self):
self.__start()
def validate(self,stn):
self.__validate(stn)
def make_qc(self, stn):
script = 'qcstats_%s.sh' % stn
command = os.path.join(TopDir, 'tmp', script)
fh = open(command, 'w')
fh.write(make_qc_script % stn)
fh.close()
os.chmod(command, stat.S_IRWXU|stat.S_IRWXG)
os.system("%s &" % command)
def exit(self):
self.__stop()
if int(self.option_get('confirmClose', '')):
if Busy.askokcancel('Do you really want to quit?', self.interior()):
self.quit()
else:
self.quit()
def __latestId(self):
idList = list(self.idents.getvalue())
try:
self.newId = [id for id in idList if id not in self.oldIdList][0]
except IndexError:
try:
self.newId = idList[-1]
except:
self.newId = ''
self.oldIdList = idList
def __details(self):
if not hasattr(self, 'cddetails'):
import ClimateDataDetails
self.cddetails = ClimateDataDetails.Details(self.interior(), self)
self.cddetails.setGeometry()
self.cddetails.setIdents()
self.cddetails.display()
def __getdir(self):
return
if not hasattr(self, 'cdgetdir'):
return tkFileDialog.askdirectory(initialdir='tmp')
def __restore(self):
if not hasattr(self, 'cdresume'):
import ClimateDataResume
self.cdresume = ClimateDataResume.Resume(self.interior(), self)
self.cdresume.setGeometry()
self.cdresume.display()
def __create(self):
stn = self.site_id.getvalue()
pil = self.pil.getvalue()
file = os.path.join(TopDir, 'data/climate/%s.hd5' % stn)
cdutils.updateIdsCfg(stn, pil, file)
self.listIdents(stn)
def __helper(self):
if not hasattr(self, 'cdhelper'):
import ClimateDataHelper
self.cdhelper = ClimateDataHelper.Helper(self.interior(), self)
self.cdhelper.setGeometry()
self.cdhelper.display()
def __scripts(self):
if not hasattr(self, 'cdscripts') and hasattr(self, 'mythread'):
import ClimateDataScripts
self.cdscripts = ClimateDataScripts.Scripts(self.interior(),update=self.mythread)
self.cdscripts.setGeometry()
self.cdscripts.display()
def __start(self):
self.__stop()
self.mythread = cdupdate.ClimateDataUpdate(name="Climate Data Update Thread", win=self)
self.mythread.start()
self.start_btn.configure(state=DISABLED)
def __continue(self):
if hasattr(self, 'mythread'):
self.mythread.bypass = True
else:
self.__start()
self.mythread.bypass = True
self.continue_btn.configure(state=DISABLED)
def __stop(self):
if hasattr(self, 'mythread'):
self.mythread.kill()
time.sleep(.1)
del self.mythread
time.sleep(.1)
self.validate_btn.configure(state=DISABLED)
def __saveLog(self):
t = time.strftime('%Y%m%d%H%M',time.gmtime())
fh = tkFileDialog.asksaveasfile(initialfile='climatedata_' + t, defaultextension='.log', initialdir='tmp')
if fh:
fh.write(self.monitor.getvalue())
fh.close()
def __showHelpDialog(self):
dialog = HelpDialog()
if dialog.winfo_ismapped():
return
#self.setGeometry(dialog)
dialog.display(_Help)
def __validate(self,stn=''):
if hasattr(self, 'mythread'):
self.mythread.validate()
else:
if stn == '': return
self.mythread = cdupdate.ClimateDataUpdate(name="Climate Data Update Thread", win=self)
self.mythread.validate(stn)
def __climate(self, choice):
AvnStart = os.path.join(TopDir, 'bin', 'avnstart.sh')
os.system('%s %s &' % (AvnStart, choice))
def __showCondClimate(self):
opts = '-f %s' %Globals.Forecaster
os.spawnlp(os.P_NOWAIT,Python, Python,
os.path.join(TopDir, 'py','avnclimate.py'),
opts)
def __commit(self):
if hasattr(self, 'mythread'):
self.mythread.commit()
def __reject(self):
if hasattr(self, 'mythread'):
self.mythread.reject()
def __setAppend(self, tag):
if tag == "Append":
self.site_id.component('entry').configure(state=DISABLED)
self.pil.component('entry').configure(state=DISABLED)
self.create_btn.configure(state=DISABLED)
self.append=True
return
self.site_id.component('entry').configure(state=NORMAL)
self.site_id.clear()
self.pil.component('entry').configure(state=NORMAL)
self.pil.clear()
self.create_btn.configure(state=NORMAL)
self.append=False
self.setSiteCreateInfo()
def __validateAFOS(self,text):
text = text.strip()
length = len(text)
if length < 8:
return Pmw.PARTIAL
value = text.upper()
if re.match('^[A-Z]{3,3}MTR[(0-9)|(A-Z)]{2,3}$', value):
return Pmw.OK
if length == 9:
return Pmw.ERROR
else:
return Pmw.PARTIAL