import numpy as np
import os
from qtpy import QtGui, QtCore, QtWidgets
import sharppy.sharptab as tab
from sharppy.sharptab.constants import *
import sharppy.databases.sars as sars
import platform
from datetime import datetime
## routine written by Kelton Halbert
## keltonhalbert@ou.edu
__all__ = ['backgroundAnalogues', 'plotAnalogues']
[docs]class backgroundAnalogues(QtWidgets.QFrame):
'''
Handles drawing the background frame for the
SARS window.
'''
def __init__(self):
''' Calls the initUI function to initialize
the background frame. Inherits from the
QtWidgets.QFrame Object.
'''
super(backgroundAnalogues, self).__init__()
self.initUI()
[docs] def initUI(self):
'''
Initializes the frame.
The frame is drawn on a QPixmap,
and is not rendered visible in this function.
'''
## set the frame stylesheet
self.setStyleSheet("QFrame {"
" background-color: rgb(0, 0, 0);"
" border-width: 1px;"
" border-style: solid;"
" border-color: #3399CC;}")
## Set the padding constants
self.lpad = 3; self.rpad = 5
self.tpad = 5; self.bpad = 5
## set the window metrics (height, width)
self.wid = self.size().width()
self.hgt = self.size().height()
self.tlx = self.rpad; self.tly = self.tpad
self.brx = self.wid; self.bry = self.hgt
fsize1 = np.floor(.09 * self.hgt)
fsize2 = np.floor(.07 * self.hgt)
fsize3 = np.floor(.06 * self.hgt)
self.tpad = np.floor(.03 * self.hgt)
## set various fonts
self.title_font = QtGui.QFont('Helvetica', fsize1)
self.plot_font = QtGui.QFont('Helvetica', fsize2)
self.match_font = QtGui.QFont('Helvetica', fsize3)
## get the metrics on the fonts. This is used to get their size.
self.title_metrics = QtGui.QFontMetrics( self.title_font )
self.plot_metrics = QtGui.QFontMetrics( self.plot_font )
self.match_metrics = QtGui.QFontMetrics( self.match_font )
## get the height of each font. This is so that we can do propper font aligning
## when drawing text
if platform.system() == "Windows":
fsize1 -= self.title_metrics.descent()
fsize2 -= self.plot_metrics.descent()
fsize3 -= self.match_metrics.descent()
self.title_font = QtGui.QFont('Helvetica', fsize1)
self.plot_font = QtGui.QFont('Helvetica', fsize2)
self.match_font = QtGui.QFont('Helvetica', fsize3)
## get the metrics on the fonts. This is used to get their size.
self.title_metrics = QtGui.QFontMetrics( self.title_font )
self.plot_metrics = QtGui.QFontMetrics( self.plot_font )
self.match_metrics = QtGui.QFontMetrics( self.match_font )
self.title_height = self.title_metrics.xHeight() + self.tpad
self.plot_height = self.plot_metrics.xHeight() + self.tpad
self.match_height = self.match_metrics.xHeight() + self.tpad
## this variable gets set and used by other functions for knowing
## where in pixel space the last line of text ends
self.ylast = self.tpad
self.text_start = 0
## placement of supercell vs hail
self.divide = (self.brx / 6)*3+10
self.selectRect = None
## The widget will be drawn on a QPixmap
self.plotBitMap = QtGui.QPixmap(self.width()-2, self.height()-2)
self.plotBitMap.fill(self.bg_color)
## plot the background
self.plotBackground()
[docs] def draw_frame(self, qp):
'''
Draws the background frame and the text headers.
'''
## initialize a white pen with thickness 1 and a solid line
pen = QtGui.QPen(self.fg_color, 1, QtCore.Qt.SolidLine)
qp.setPen(pen)
## make the initial x value relative to the width of the frame
x1 = self.brx / 6
self.ylast = self.tpad
## use the larger title font to plot the title, and then
## add to self.ylast the height of the font + padding
qp.setFont(self.title_font)
rect0 = QtCore.QRect(0, self.ylast, self.brx, self.title_height)
qp.drawText(rect0, QtCore.Qt.TextDontClip | QtCore.Qt.AlignCenter,
'SARS - Sounding Analogue System')
self.ylast += (self.title_height + self.tpad)
## draw some lines to seperate the hail and supercell windows,
## then add to the running sum for vertical placement
qp.drawLine(0, self.ylast, self.brx, self.ylast)
qp.drawLine(self.brx/2, self.ylast, self.brx/2, self.bry)
self.ylast += self.tpad
## plot the text for the hail and supercell windows using the running
## ylast sum
qp.setFont(self.plot_font)
rect1 = QtCore.QRect(x1*1, self.ylast, x1, self.plot_height)
qp.drawText(rect1, QtCore.Qt.TextDontClip | QtCore.Qt.AlignCenter,
'SUPERCELL')
rect2 = QtCore.QRect(x1*4, self.ylast, x1, self.plot_height)
qp.drawText(rect2, QtCore.Qt.TextDontClip | QtCore.Qt.AlignCenter,
'SGFNT HAIL')
## Add to the running sum once more for future text
self.ylast += (self.title_height)
## the hail and supercell windows both need to have a vertical starting reference
self.text_start = self.ylast
[docs] def resizeEvent(self, e):
'''
Handles when the window gets resized.
'''
self.initUI()
[docs] def plotBackground(self):
'''
Handles drawing the text background.
This draws onto a QPixmap.
'''
## initialize a QPainter objext
qp = QtGui.QPainter()
qp.begin(self.plotBitMap)
## draw the frame
self.draw_frame(qp)
qp.end()
[docs]class plotAnalogues(backgroundAnalogues):
updatematch = QtCore.Signal(str)
'''
Handles the non-background plotting
of the SARS window. This inherits a
backgroundAnalogues Object that
takes care of drawing the frame
background onto a QPixmap. This will
inherit that QPixmap and continue
drawing on it, finally rendering the
QPixmap via the function paintEvent.
'''
def __init__(self):
'''
Initializes the data needed for drawing the
data onto the QPixmap.
Parameters
----------
prof: a Profile object
self.view.setDataSource(self.data_sources[self.model], self.run)
'''
## get the surfce based, most unstable, and mixed layer
## parcels to use for indices, as well as the sounding
## profile itself.
self.bg_color = QtGui.QColor('#000000')
self.fg_color = QtGui.QColor('#ffffff')
self.use_left = False
super(plotAnalogues, self).__init__()
self.prof = None
[docs] def setProf(self, prof):
self.prof = prof
if self.use_left:
self.hail_matches = prof.left_matches
self.sup_matches = prof.left_supercell_matches
else:
self.hail_matches = prof.right_matches
self.sup_matches = prof.right_supercell_matches
self.ybounds_hail = np.empty((len(self.hail_matches[0]),2))
self.ybounds_sup = np.empty((len(self.sup_matches[0]),2))
self.ylast = self.tpad
self.clearData()
self.plotBackground()
self.plotData()
self.update()
[docs] def setPreferences(self, update_gui=True, **prefs):
self.bg_color = QtGui.QColor(prefs['bg_color'])
self.fg_color = QtGui.QColor(prefs['fg_color'])
if update_gui:
if self.use_left:
self.hail_matches = self.prof.left_matches
self.sup_matches = self.prof.left_supercell_matches
else:
self.hail_matches = self.prof.right_matches
self.sup_matches = self.prof.right_supercell_matches
self.ybounds_hail = np.empty((len(self.hail_matches[0]),2))
self.ybounds_sup = np.empty((len(self.sup_matches[0]),2))
self.clearData()
self.plotBackground()
self.plotData()
self.update()
[docs] def setDeviant(self, deviant):
self.use_left = deviant == 'left'
if self.use_left:
self.hail_matches = self.prof.left_matches
self.sup_matches = self.prof.left_supercell_matches
else:
self.hail_matches = self.prof.right_matches
self.sup_matches = self.prof.right_supercell_matches
self.ybounds_hail = np.empty((len(self.hail_matches[0]),2))
self.ybounds_sup = np.empty((len(self.sup_matches[0]),2))
self.clearData()
self.plotBackground()
self.plotData()
self.update()
[docs] def resizeEvent(self, e):
'''
Handles when the window is resized.
Parameters
----------
e: an Event object
'''
super(plotAnalogues, self).resizeEvent(e)
## if the window is resized, replot the data
## in the QPixmap.
self.plotData()
[docs] def paintEvent(self, e):
'''
Handles drawing the QPixmap onto the
widget.
Parameters
----------
e: an Event object
'''
super(plotAnalogues, self).paintEvent(e)
qp = QtGui.QPainter()
qp.begin(self)
qp.drawPixmap(1, 1, self.plotBitMap)
qp.end()
[docs] def clearData(self):
'''
Handles the clearing of the pixmap
in the frame.
'''
self.plotBitMap = QtGui.QPixmap(self.width(), self.height())
self.plotBitMap.fill(self.bg_color)
[docs] def plotData(self):
'''
Handles the drawing of the SARS
matches onto the QPixmap.
'''
if self.prof is None:
return
## initialize a QPainter object
qp = QtGui.QPainter()
qp.begin(self.plotBitMap)
## draw the matches
self.drawSARS(qp, type='HAIL')
self.drawSARS(qp, type='TOR')
qp.end()
[docs] def drawSARS(self, qp, **kwargs):
'''
This draws the SARS matches.
Parameters
----------
qp: a QtGui.QPainter Object
type: A string for the type of
SARS matches. 'HAIL' for
hail matches and 'TOR' for
supercell matches.
'''
x1 = self.brx / 6
## get the type of matches, and determine
## which side of the frame the matches will
## be plotted in.
type = kwargs.get('type', 'HAIL')
if type == 'TOR':
self.matches = self.sup_matches
sigstr = 'TOR'
## this is the horizontal placement
## of the text in the frame. Valid
## integers are from 0-6.
place = 1
## the quality match date [0] and the type/size
## [1] palcement are set in this tuple.
place2 = (self.lpad, (self.brx/2.) - x1 * 3./4. + 2)
self.ybounds = self.ybounds_sup
else:
self.matches = self.hail_matches
sigstr = 'SIG'
## this is the horizontal placement
## of the text in the frame. Valid
## integers are from 0-6.
place = 4
## the quality match date [0] and the type/size
## [1] palcement are set in this tuple.
place2 = (x1*3+7, x1*5.5-5)
self.ybounds = self.ybounds_hail
## if there are no matches, leave the function to prevent crashing
pen = QtGui.QPen(self.fg_color, 1, QtCore.Qt.SolidLine)
if self.matches[0].size == 0:
pen.setColor(self.fg_color)
qp.setPen(pen)
qp.setFont(self.match_font)
## draw the text 2/5 from the top
rect2 = QtCore.QRect(x1*place, self.bry * (2./5.), x1, self.match_height)
qp.drawText(rect2, QtCore.Qt.TextDontClip | QtCore.Qt.AlignCenter,
'No Quality Matches')
else:
## set the pen, font
qp.setPen(pen)
qp.setFont(self.plot_font)
## self.ylast has to be this way in order to plot relative to the bottom
self.ylast = (self.bry - self.bpad*3)
## get various data to be plotted
sig_prob = tab.utils.INT2STR( np.around( self.matches[-1]*100 ) )
sig_str = 'SARS: ' + sig_prob + '% ' + sigstr
num_matches = tab.utils.INT2STR( self.matches[-3] )
match_str = '(' + num_matches + ' loose matches)'
## if there are more than 0 loose matches, draw
## draw the match statistics
if self.matches[-3] > 0:
qp.setFont(self.match_font)
## set the color of the font
if self.matches[-1]*100. >= 50.:
pen.setColor(QtCore.Qt.magenta)
qp.setPen(pen)
else:
pen.setColor(self.fg_color)
qp.setPen(pen)
## draw the text
rect0 = QtCore.QRect(x1*place, self.ylast, x1, self.match_height)
qp.drawText(rect0, QtCore.Qt.TextDontClip | QtCore.Qt.AlignCenter,
sig_str)
## since we start at the bottom and move up, subtract the height instead of add
self.ylast -= (self.match_height + self.bpad)
rect1 = QtCore.QRect(x1*place, self.ylast, x1, self.match_height)
qp.drawText(rect1, QtCore.Qt.TextDontClip | QtCore.Qt.AlignCenter,
match_str)
## If not, don't do anything
else:
pass
## if there are no quality matches, let the gui know
if len(self.matches[0]) == 0:
pen.setColor(self.fg_color)
qp.setPen(pen)
qp.setFont(self.match_font)
## draw the text 2/5 from the top
rect2 = QtCore.QRect(x1*place, self.bry * (2./5.), x1, self.match_height)
qp.drawText(rect2, QtCore.Qt.TextDontClip | QtCore.Qt.AlignCenter,
'No Quality Matches')
## if there are more than 0 quality matches...
else:
pen.setColor(self.fg_color)
qp.setPen(pen)
qp.setFont(self.match_font)
## start the vertical sum at the reference point
self.ylast = self.text_start
idx = 0
## loop through each of the matches
i = 0
for m in self.matches[0]:
mdate, mloc = m.split(".")
mdate = datetime.strptime(mdate, "%y%m%d%H").strftime("%d %b %y %HZ")
match_str = "%s (%s)" % (mdate, mloc)
## these are the rectangles that matches will plot inside of
rect3 = QtCore.QRect(place2[0], self.ylast, x1, self.match_height)
rect4 = QtCore.QRect(place2[1], self.ylast, x1, self.match_height)
self.ybounds[i, 0] = self.ylast
self.ybounds[i, 1] = self.ylast + self.match_height
## size or type is used for setting the color
size = self.matches[1][idx]
if type == 'TOR':
size_str = size[:-3]
if size.startswith('SIG'):
pen.setColor(QtGui.QColor(RED))
qp.setPen(pen)
elif size.startswith('WEAK'):
pen.setColor(QtGui.QColor(LBLUE))
qp.setPen(pen)
elif size.startswith('NON'):
pen.setColor(QtGui.QColor(LBROWN))
qp.setPen(pen)
else:
size_str = str( format(size, '.2f' ) )
if size >= 2.0:
pen.setColor(QtGui.QColor(RED))
qp.setPen(pen)
else:
pen.setColor(QtGui.QColor(LBLUE))
qp.setPen(pen)
## draw the text
qp.drawText(rect3, QtCore.Qt.TextDontClip | QtCore.Qt.AlignLeft, match_str )
qp.drawText(rect4, QtCore.Qt.TextDontClip | QtCore.Qt.AlignLeft, size_str )
## is there is a selected match, draw the bounds
if self.selectRect is not None:
pen.setColor(QtGui.QColor(LBLUE))
qp.setPen(pen)
topLeft = self.selectRect.topLeft()
topRight = self.selectRect.topRight()
bottomLeft = self.selectRect.bottomLeft()
bottomRight = self.selectRect.bottomRight()
qp.drawLine(topLeft, topRight)
qp.drawLine(bottomLeft, bottomRight)
idx += 1
i += 1
## add to the running vertical sum
vspace = self.match_height
if platform.system() == "Windows":
vspace += self.match_metrics.descent()
self.ylast += vspace
self.ylast = self.tpad
[docs] def mousePressEvent(self, e):
if self.prof is None or (len(self.sup_matches[0]) == 0 and len(self.hail_matches[0]) == 0):
return
pos = e.pos()
## is this a supercell match?
if pos.x() < (self.brx / 2.):
## loop over supercells
for i, bound in enumerate(self.ybounds_sup):
if bound[0] < pos.y() and bound[1] > pos.y():
filematch = sars.getSounding(self.sup_matches[0][i], "supercell")
print(filematch)
self.updatematch.emit(filematch)
## set the rectangle used for showing
## a selected match
self.selectRect = QtCore.QRect(0, self.ybounds_sup[i, 0],
self.brx / 2.,
self.ybounds_sup[i, 1] - self.ybounds_sup[i, 0])
break
## is this a hail match?
elif pos.x() > (self.brx / 2.):
## loop over hail
for i, bound in enumerate(self.ybounds_hail):
if bound[0] < pos.y() and bound[1] > pos.y():
filematch = sars.getSounding(self.hail_matches[0][i], "hail")
print(filematch)
self.updatematch.emit(filematch)
## set the rectangle used for showing
## a selected match
self.selectRect = QtCore.QRect(self.brx / 2., self.ybounds_hail[i, 0],
self.brx / 2.,
self.ybounds_hail[i, 1] - self.ybounds_hail[i, 0])
break
self.clearData()
self.plotBackground()
self.plotData()
self.update()
#logging.debug("Calling plotAnaloges.parentWidget().setFocus()")
self.parentWidget().setFocus()
[docs] def setSelection(self, filematch):
"""
Load in the SARS analog you've clicked.
"""
match_name = os.path.basename(filematch)
# print("\n\nSETSELECION:", match_name, filematch, self.sup_matches[0], self.hail_matches[0])
sup_matches = [sars.getSounding(f, 'supercell').split('/')[-1] for f in self.sup_matches[0]]
hail_matches = [sars.getSounding(f, 'hail').split('/')[-1] for f in self.hail_matches[0]]
# print(sup_matches, hail_matches)
if match_name in sup_matches:
idx = np.where(np.asarray(sup_matches, dtype=str) == match_name)[0][0]
lbx = 0.
ybounds = self.ybounds_sup
if match_name in hail_matches:
idx = np.where(np.asarray(hail_matches, dtype=str) == match_name)[0][0]
lbx = self.brx / 2.
ybounds = self.ybounds_hail
self.selectRect = QtCore.QRect(lbx, ybounds[idx, 0],
self.brx / 2., ybounds[idx, 1] - ybounds[idx, 0])
self.clearData()
self.plotBackground()
self.plotData()
self.update()
self.parentWidget().setFocus()
[docs] def clearSelection(self):
self.selectRect = None
self.clearData()
self.plotBackground()
self.plotData()
self.update()
#print(self.parent, self.parentWidget())
#self.setParent(self.parent)
self.parentWidget().setFocus()
if __name__ == '__main__':
app_frame = QtGui.QApplication([])
#tester = plotText(['sfcpcl', 'mlpcl', 'mupcl'])
tester = plotAnalogues()
#tester.setProf()
tester.show()
app_frame.exec_()