awips2/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/NDFD_QPF_Checks.py

1327 lines
72 KiB
Python
Raw Normal View History

2022-05-05 12:34:50 -05:00
##
# 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.
##
# ----------------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without technical
# support, and with no warranty, express or implied, as to its usefulness for
# any purpose.
#
# NDFD_QPF_Checks (was SnowAmtQPFPoPWxCheck)
#
# Author: Jay Smith, WFO Fairbanks, jay.smith@noaa.gov, 907-458-3721
# SnowAmtQPFPoPWxCheck Incarnation
# Version: 1.0.0, 09/14/2006 - Initial version
# 1.0.1, 10/12/2006 - Added PoP/QPF check at request of DSPAC
# 1.0.2, 10/18/2006 - Changed PoP/QPF check to treat the PoP as
# floating. Instead of checking each individual PoP grid
# against its corresponding QPF grid, the max of all the
# PoP grids overlapping a QPF grid will be checked.
# 1.1.0, 01/25/2007 - Added options to choose which checks to run.
# Reorganized code so that each check is its own method.
# Added a check for QPF and Wx. Added highlighting for the
# created temporary grids.
# 1.1.1, 02/01/2007 - Changed the SnowAmt/Wx check to return
# consistent results for SnowAmt > 0 and Wx grid containing
# S, SW, or IP regardless of whether the frozen precip is
# mixed with freezing and/or liquid precip.
# 1.2.0, 02/13/2007 - Added a configuration option to provide a CWA
# edit area to run the procedure over. A bad edit area or no
# edit area will result in running over the whole domain.
# Modified the SnowAmt/Wx and QPF/Wx checks to handle two
# cases. Case 1: The SnowAmt/QPF grid is 6-hr long and starts
# at 00, 06, 12, or 18 UTC. Then only one of the corresponding
# Wx grids has to meet the consistency rule. Case 2: The
# SnowAmt/QPF grid does not meet the case 1 definition. Then
# all of the corresponding Wx grids must meet the consistency
# rule.
# NDFD_QPF_Checks Incarnation
# Version: 1.0.0, 04/04/2007 - The program now requires the presence of the
# following 3 grids: QPF6hr, SnowAmt6hr, and PoP12hr. The 2
# 6-hr grids are expected to start at 00, 06, 12, or 18 UTC
# and be exactly 6 hours long. The PoP12hr grids are expected
# to start at 00 or 12 UTC and be exactly 12 hours long.
# The procedure still needs the PoP and Wx grids. With these
# grid changes, the procedure can now finally perform the last
# of the required NDFD checks. This also means there is no
# longer the need for two check cases in the two "Wx" checks.
# Now, Wx is consistent if any of its grids have the appropriate
# type. The procedure can now be run in a "quiet" mode if
# called from another procedure. Any messages generated in
# "quiet" mode will be of "R" severity so no pop-up messages
# are generated but the information is still available from the
# GFE status bar area. All error messages are templated in a
# dictionary in a separate method. This allows me to put all
# the triple-quoted strings, which ignore indentation, in one
# location. The code for checking for locked grids was also
# templated in its own method, which chopped about 15 lines of
# code off the front of each check method. There were a couple
# of places where I was applying the "tolerance" values
# incorrectly, which have been fixed. I dropped the
# "Inconsistent" label from all temporary grid names. I was
# making those grid names so long, they didn't actually fit
# in the spatial editor window when the grid manager was on
# the left. Temporary grid names now are just a concatenation
# of the two grids used in the check.
# Version: 1.1.0, The logic for the handling of inconsistencies in the
# SnowAmt6hr/Wx, QPF6hr/Wx, and PoP12hr/QPF6hr checks could
# result in false positive inconsistencies. This is because
# I was checking for inconsistencies on each of these grids
# individually when I needed to be checking the cumulative
# inconsistencies of these grids. With the new logic, if any
# of these checks has inconsistencies, the resulting temporary
# grid will have the same time range as the "controlling" grid,
# which is the first grid listed in each check name. Also, I
# have enforced the precision of the SnowAmt6hr and QPF6hr
# grids in the methods where they are used. SmartScript has a
# method called "around" that does this. I ran into an issue
# when this procedure was called from NDFD_QPF_Suite. I am
# unable to figure out how to uncache the QPF6hr, SnowAmt6hr,
# and PoP12hr grids after they are generated by the
# Collaborate_PoP_SnowAmt_QPF procedure. For the QPF6hr and
# SnowAmt6hr grids, this means getGrids is getting them from
# the cache, which means these float parameters have machine
# precision. This is utterly unacceptable. I have to have
# ifpServer precision. Now, I think I've ensured this.
# 1.1.1, 05/02/2007 - There have been instances of unexpected procedure
# performance if the NDFD QPF parameters are not visible in the
# grid manager when the procedure is run. The procedure will
# now require the existence of a weather element group which
# contains just the NDFP QPF parameters.
# 1.2.0, 05/03/2007 - Upon further review, the unexpected procedure
# performance arises when some of the NDFD QPF parameters are
# not present in the grid manager. However, I do not need to
# load a weather element group to make the parameters present.
# I can use the loadParm command on each element instead. Given
# this, the "weGroup" configuration has been removed. Also,
# some people believe the lock checking is overly stringent.
# To some extent, I agree. For the purposes of this procedure,
# other GFE users can have any of the NDFD QPF parmeteres
# locked. The user running the procedure, however, cannot have
# any of the parameters locked; i.e., that person must save
# those elements before running the procedure.
# The procedure performs the following checks:
# 1. If SnowAmt6hr present and >= 0.5 inches, then corresponding QPF6hr grids
# must be >= 0.01 inches.
# 2. If SnowAmt6hr >= 0.1 inches then at least one of the corresponding Wx
# grids must have S, SW, or IP.
# 3. If QPF6hr > 0.0, then at least one of the corresponding PoP grids must
# be > 0
# 4. If QPF6hr > 0.0 then at least one of the corresponding Wx grids must have
# R, RW, S, SW, RS, IP, L, ZR, ZL.
# 5. If PoP12hr >= 50%, then at least one of the corresponding QPF6hr grids
# must be >= 0.0.
# For all of the checks above, if the initial threshold is not exceeded, then
# the two grids are consistent by definition. In other words:
# 1. If SnowAmt6hr < 0.5, then SnowAmt6hr and QPF6hr are always consistent.
# 2. If SnowAmt6hr < 0.1, then SnowAmt6hr and Wx are always consistent.
# 3. If QPF6hr = 0.0, then QPF6hr and PoP are always consistent.
# 4. If QPF6hr = 0.0, then QPF6hr and Wx are always consistent.
# 5. If PoP12hr < 50%, then PoP12hr and QPF6hr are always consistent.
# For the Wx checks above, only the Wx type is considered.
#
# ****** NOTE NOTE NOTE NOTE ******
# The program checks the PoP12hr, QPF6hr, and SnowAmt6hr grids to make sure
# their time constraints are met. For any grid where the time constraint is
# violated, those grids are not checked. To reiterate the time constraints:
# PoP12hr: starts at either 00 or 12 UTC and is exactly 12 hours duration
# QPF6hr: starts at 00, 06, 12, or 18 UTC and is exactly 6 hours duration
# SnowAmt6hr: starts at 00, 06, 12 or 18 UTC and is exactly 6 hours duration
# ****** NOTE NOTE NOTE NOTE ******
#
# If discrepancies are found, then the "bad" grids will be highlighted.
# Temporary grids showing where the discrepancies occur will be created and
# also highlighted.
#
# I've written this code such that it's optimized to minimize memory usage
# (at least I think I've done that). As a result, it's not particularly
# optimized for ifpServer database access. In fact, I retrieve the various
# grids from the ifpServer database many times during the procedure's run.
# This will have an impact on how fast the procedure runs (it'll run slower
# than if I had optimized for ifpServer database access). The choice to favor
# memory optimization comes from my belief that there are still "memory leak"
# problems in the GFE and that the consequences of those problems will be most
# manifest when this procedure is most likely to be run (near the end of the
# shift). Funky memory problems are a prime cause of funky application
# behavior like application crashes or spontaneous logouts. So, this procedure
# basically reads a grid into memory, keeps it as long as it's needed, and
# then discards it.
#
# Finally, this procedure is also intended to provide an example to other
# developers of how to write and document code. I have reservations as to how
# well I've succeeded at that task. The code is heavily documented, probably
# excessively so. Also, it's not as well as organized as it could be. As you
# look through the various methods, it should become quickly apparent that
# there is a lot of repeated code. I've consciously left some the code this
# way in the hopes that it will be easier to understand by more novice
# programmers and because the code hasn't quite grown to the point where
# updating the repeating code is onerous or overly error-prone. It would be
# better to capture the repeating code in separate methods, but keeping track
# of the where you are in the code becomes harder the more you have to jump
# around from method to method. Anyone who has ever worked with the text
# formatters can sympathize with that. As with all things, there are trade-
# offs involved. UPDATE: 4/3/2007 - Starting with the first NDFD_QPF_Checks
# version, I consolidated quite a bit of the repeating code into separate
# methods. So, there's some improvement on that front.
#
# Acknowledgement:
# Many of the Python "tricks" I use in this procedure I learned from
# reading/perusing the following book: Python Cookbook, Alex Martelli &
# David Ascher, eds., 2002, O'Reilly and Associates
# ----------------------------------------------------------------------------
##
# This is an absolute override file, indicating that a higher priority version
# of the file will completely replace a lower priority version of the file.
##
MenuItems = ["Consistency"]
VariableList = [
('Check_Cleanup', 'Check', 'radio', ['Check', 'Cleanup']),
('Run SnowAmt6hr/QPF6hr Check?', ['Yes'], 'check', ['Yes']),
('Run SnowAmt6hr/Wx Check?', ['Yes'], 'check', ['Yes']),
('Run QPF6hr/PoP Check?', ['Yes'], 'check', ['Yes']),
('Run QPF6hr/Wx Check?', ['Yes'], 'check', ['Yes']),
('Run PoP12hr/QPF6hr Check?', ['Yes'], 'check', ['Yes']),
('If "Cleanup" is selected, then only cleanup actions will run.\nNo checks will be made, regardless of the above settings.', '', 'label'),
]
#### Config section
# Both the QPF and SnowAmt grids have values which are floating point
# numbers. This means comparisons must use a tolerance value. In other
# words, 0.5 may be represented in machine numbers as 0.49999999999 or
# 0.500000000001. By specifying a tolerance value, we account for the
# vagaries of machine representation of floating point numbers while
# keeping the precision of the comparisons to acceptable levels. Depending
# on the comparison being done, the tolerance value will be added to or
# subtracted from the comparison value to allow for machine error in the
# floating point number representation.
# By default in the GFE, QPF precision is to the nearest one-hundredth while
# SnowAmt precision is to the nearest tenth.
qpfTol = 0.00001 # 1/100,000 tolerance vs 1/100 precision
snowAmtTol = 0.0001 # 1/10,000 tolerance vs 1/10 precision
# Inconsistent grid highlight color. One size fits all. To turn off
# highlighting, set the variable to the empty string, ''.
inconGridColor = 'red'
# Temporary grid highlight color. One size fits all. To turn off highlighting,
# set the variable to the empty string, ''.
tempGridColor = 'orange'
# Name of CWA edit area to use instead of running the procedure over the
# whole domain. Set to the empty string, '', if you want the procedure to
# always run over the whole domain. If the procedure has a problem with the
# edit area you provide, it will run over the whole domain. You should probably
# choose an edit area that is slightly larger than your entire CWA. It's
# possible that when mapping your GFE grids to NDFD grids that the NDFD thinks
# some GFE grid cells are in your CWA that the GFE does not think are in your
# CWA. Using an edit area slightly larger than the CWA, like the ISC_Send_Area
# which is the mask used when sending grids to the NDFD, should eliminate the
# possibibilty of the NDFD intermittently flagging CWA border "points" as
# inconsistent. Note: running the procedure over a subset of the entire GFE
# domain does not really provide any performance gains. Given the way the
# underlying array data structure works, calculations are almost always made
# at every single grid point first and then a mask is applied to limit the
# meaningful results to the edit area. For the purposes of this procedure, the
# values outside the edit area are set to the appropriate "consistent" result.
# The real benefit of this option is it limits the inconsistent results to the
# areas the forecaster really cares about, which should lessen the workload of
# using this procedure. Marine Offices: Make sure the edit area provided
# includes your marine zones.
cwaEditArea = 'ISC_Send_Area'
#### Config section end
import SmartScript
from numpy import *
class Procedure (SmartScript.SmartScript):
def __init__(self, dbss):
SmartScript.SmartScript.__init__(self, dbss)
def __cleanup(self, timeRange):
# Remove any temporary grids created previously. Turn off any
# previous highlighting.
for element in (
'SnowAmt6hrQPF6hr', 'SnowAmt6hrWx', 'QPF6hrPoP', 'QPF6hrWx',
'PoP12hrQPF6hr'):
try:
# From SmartScript
self.unloadWE('Fcst', element, 'SFC')
except:
# A failure is almost certainly no grids to unload.
pass
# Turn off any highlights. From SmartScript
self.highlightGrids(
'Fcst', 'SnowAmt6hr', 'SFC', timeRange, inconGridColor, on=0)
self.highlightGrids(
'Fcst', 'QPF6hr', 'SFC', timeRange, inconGridColor, on=0)
self.highlightGrids(
'Fcst', 'Wx', 'SFC', timeRange, inconGridColor, on=0)
self.highlightGrids(
'Fcst', 'PoP', 'SFC', timeRange, inconGridColor, on=0)
self.highlightGrids(
'Fcst', 'PoP12hr', 'SFC', timeRange, inconGridColor, on=0)
return
def __checkConfigValueTypes(self):
# Make sure the values provided in the configuration section are the
# correct type.
# Later in the code are two methods devoted to creating error
# messages. The error messages here could pop up in quite a large
# number of different combinations, which makes capturing them in the
# later methods very complex. Rather than do that and considering
# that these error messages should never appear once the procedure
# is correctly set up, I decided to leave them here. There always has
# to be an exception. :-)
import types
message = ''
badValues = False
if not type(qpfTol) is float:
message = '%sThe "qpfTol" variable is not defined as a floating point value. Please contact your IFPS focal point to fix this.\n' % message
badValues = True
if not type(snowAmtTol) is float:
message = '%sThe "snowAmtTol" variable is not defined as a floating point value. Please contact your IFPS focal point to fix this.\n' % message
badValues = True
if not type(inconGridColor) is str:
message = '%sThe "inconGridColor" variable is not defined as a string value. Please contact your IFPS focal point to fix this.\n' % message
badValues = True
if not type(tempGridColor) is str:
message = '%sThe "tempGridColor" variable is not defined as a string value. Please contact your IFPS focal point to fix this.\n' % message
badValues = True
if not type(cwaEditArea) is str:
message = '%sThe "cwaEditArea" variable is not defined as a string value. Please contact your IFPS focal point to fix this.\n' % message
badValues = True
if badValues:
message = '%sYou will not be able to run the procedure until the problem is corrected.' % message
# The next two commands are from SmartScript
self.statusBarMsg(message, 'U')
self.cancel()
return
def __checkLockedStatus(self, elementList):
# There can be a significant difference between the values stored
# in memory and the values returned from the database. This is because
# when values are saved, the element's precision (as defined in
# serverConfig.py/localConfig.py) is enforced. Values in memory do not
# have the element's precision enforced; in fact, they have the
# machine precision of the underlying data type.
# At the beginning of each check method, a call to this method is
# made to make sure the grids are saved. A check method will not run
# if the grids it's to check are not saved. This method will return
# a list of boolean values indicating if the elements are locked by me
# and then if the elements are locked by other.
# The lockedByMe and lockedByOther methods are from SmartScript
lockedByMe = []
lockedByOther = []
for element in elementList:
if self.lockedByMe(element, 'SFC'):
lockedByMe.append(True)
else:
lockedByMe.append(False)
## if self.lockedByOther(element, 'SFC'):
## lockedByOther.append(True)
## else:
## lockedByOther.append(False)
lockedByOther.append(False)
return lockedByMe + lockedByOther
def __getMsgSeverity(self, severity):
# For calls to self.statusBarMsg where I intended the severity to be
# something other than 'R', this method is now called to determine
# what the severity should be. This procedure can be called from
# another procedure in such a way as to suppress the pop-up type of
# status bar messages. This is done by passing in a varDict with a
# 'Quiet' key which evaluates to 'True'. For those situations, the
# procedure defers to the calling program and turns any non 'R'
# severities into 'R' severity. This allows the message to be
# communicated still to the GFE session, but only via the 'Status'
# line area of the GFE. When run interactively from the GFE, the
# severity this procedure assigns to a message will be used.
# This method is actually invoked in the call to statusBarMsg in
# place of the severity string. As long as the entry for the severity
# in statusBarMsg evaluates to a string type, statusBarMsg will be
# 'happy'.
if self._quiet:
return 'R'
return severity
def __checkTC(self, element, gridTR):
# The QPF6hr, SnowAmt6hr, and PoP12hr grids have specific time
# constraints that the respective grids must adhere to. In other
# words, it's not acceptable to this procedure for the QPF6hr grid,
# for example, to be stretched to 12 hours long. This method makes
# sure each of the grids exactly conforms to the time constraint
# defintion. The method returns True if good, False if bad. If, for
# some reason, the method gets called with some other element, the
# method will return True.
if element == 'QPF6hr' or element == 'SnowAmt6hr':
startHourTup = (0, 6, 12, 18)
goodDuration = 6 * 3600
elif element == 'PoP12hr':
startHourTup = (0, 12)
goodDuration = 12 * 3600
else:
return True
if gridTR.startTime().hour in startHourTup and \
gridTR.duration() == goodDuration:
return True
return False
def _runSnowAmt6hrQPF6hrCheck(self, timeRange):
# This method implements the check that if SnowAmt6hr >= 0.5, then
# QPF6hr must be >= 0.01.
# If there are locks, post urgent messages and return from the method.
snowLockMe, qpfLockMe, snowLockOther, qpfLockOther = \
self.__checkLockedStatus(['SnowAmt6hr', 'QPF6hr'])
if snowLockMe or qpfLockMe or snowLockOther or qpfLockOther:
# Something's locked, create messages.
self._makeLockMsgs(
snowLockMe, qpfLockMe, snowLockOther, qpfLockOther,
'SnowAmt6hr', 'QPF6hr', 'snowLockMe', 'qpfLockMe',
'snowLockOther', 'qpfLockOther', 'SnowAmt6hr/QPF6hr')
return
# Make sure there are actually SnowAmt6hr grids in the time range.
# The self.getGridInfo command will return an empty list if there
# are no grids in the time range. This is more efficient than using
# self.getGrids with mode='First' and noDataError=0.
# The getGridInfo method is from SmartScript
snowAmtInfoList = self.getGridInfo(
'Fcst', 'SnowAmt6hr', 'SFC', timeRange)
if snowAmtInfoList == []:
message = self._getMsg(
'noGrids', element='SnowAmt6hr', timeRange=timeRange,
method='SnowAmt6hr/QPF6hr')
# The statusBarMsg method is from SmartScript
self.statusBarMsg(message, self.__getMsgSeverity('U'))
# I return instead of aborting because the user may have asked for
# other tests that do not have missing grid problems.
return
# One might ask why I don't just return the result of self.getGrids
# to a variable and iterate over that. I'm trying to minimize the
# memory footprint of the procedure. Reading all the grids into a
# variable could be a fairly large memory hit. I believe the construct
# below only reads one SnowAmt6hr grid at a time into memory, the one
# that's being checked. (But I can't find the reference that supports
# my belief.) By using the cache=0 switch on all the self.getGrids
# command, I prevent the GFE from saving the grids into memory for me.
# (At least, that's what I think the cache=0 switch does. The
# SmartScript documentation is a little vague on this point.)
# The Python builtin command enumerate loops over an iterable object
# and returns a 2-tuple containing the current index of the
# iteration and the object at that index. In cases where I need
# both the index and the object, I think this construct is more
# elegant than:
# for i in xrange(len(iterableObject)):
# object = iterableObject[i]
for snowAmtIndex, snowAmtGrid in enumerate(self.getGrids(
'Fcst', 'SnowAmt6hr', 'SFC', timeRange, mode='List', cache=0)):
gridTR = snowAmtInfoList[snowAmtIndex].gridTime()
# Check to make sure the grid meets it's time constraints.
if not self.__checkTC('SnowAmt6hr', gridTR):
message = self._getMsg(
'badTC', element='SnowAmt6hr', timeRange=gridTR)
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# around is from SmartScript
snowAmtGrid = around(snowAmtGrid, 1)
# The greater_equal method is from Numeric. For the given array
# and threshold, a new array of the same dimensions as the input
# array is returned. The new array has the value 1 where the
# input array was greater than or equal to the threshold and
# has the value 0 elsewhere.
# The getGridInfo method is from SmartScript
halfInchMask = greater_equal(snowAmtGrid, 0.5 - snowAmtTol)
qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', gridTR)
# There should always be more QPF6hr grids than SnowAmt6hr grids,
# so if qpfInfoList is empty, then there are missing QPF6hr
# grids. Otherwise, qpfInfoList will have length 1 because
# SnowAmt6hr and QPF6hr have the same time constrain. However,
# the QPF6hr grid that overlaps the SnowAmt6hr grid will still
# need to be checked to make sure it hasn't been stretched.
if qpfInfoList == []:
message = self._getMsg(
'noGrids', element='QPF6hr', timeRange=gridTR,
method='SnowAmt6hr/QPF6hr')
# The statusBarMsg is from SmartScript
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# I don't need the noDataError=0 in the self.getGrids call
# because if there were no grids in the gridTR, the previous
# if block would have caught that.
# The getGrids method is from SmartScript
qpfGrid = self.getGrids(
'Fcst', 'QPF6hr', 'SFC', gridTR, mode='First', cache=0)
if not self.__checkTC('QPF6hr', gridTR):
message = self._getMsg(
'badTC', element='QPF6hr', timeRange=gridTR)
# The statusBarMsg method is from SmartScrtipt
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# If we get here, then we have a SnowAmt6hr grid and a QPF6hr
# grid which meet their time constraints and are ready to be
# compared.
# around is from SmartScript
qpfGrid = around(qpfGrid, 2)
# The less method is from Numeric. It behaves analogously to
# the greater_equal method described above using less than for
# the comparison.
qpfMask = less(qpfGrid, 0.01 - qpfTol)
# The following is the "truth" table for the logical
# comparison.
# SnowAmt6hr >= 0.5, 1; SnowAmt6hr < 0.5, 0
# QPF6hr < 0.01, 1; QPF6hr >= 0.01, 0
# SnowAmt6hr >= 0.5 (1) and QPF6hr < 0.01 (1) = 1 (Bad result)
# SnowAmt6hr >= 0.5 (1) and QPF6hr >= 0.01 (0) = 0 (Good result)
# SnowAmt6hr < 0.5 (0) and QPF6hr < 0.01 (1) = 0 (Good result)
# SnowAmt6hr < 0.5 (0) and QPF6hr >= 0.01 (0) = 0 (Good result)
# The logical_and method is from Numeric. A logical and comparison
# results in a "True" value if both compared elements are "True".
# Otherwise, the result is "False".
consistMask = logical_and(halfInchMask, qpfMask)
# Now, apply the CWA mask. There's an assumption here that
# all offices will use a mask and provide a valid one, which
# means this step does something meaningful. If that assumption
# does not hold, then the next statement doesn't actually
# change anything, even though each and every grid point has a
# comparison check made.
# where is from Numeric. The first argument is a mask.
# The second argument is/are the value/values to use at the
# array points where the mask is one. The third argument
# is/are the value/values to use at the array points
# where the mask is zero. For this comparison, I want
# the values of consistMask where self.cwaMask is one and
# I want the "good result", which is zero, where
# self.cwaMask is zero.
consistMask[logical_not(self.cwaMask)] = 0
# The ravel and sometrue methods are from Numeric.
if sometrue(ravel(consistMask)):
# The ravel method reduces the rank of the array by one.
# Since we had a 2-d array, the ravel function creates a
# 1-d array (a vector) such that reading the 2-d array from
# left-to-right, top-to-bottom returns the same values as
# reading the 1-d array from left-to-right. The sometrue
# method performs a logical or on subsequent element pairs
# in the 1-d array and returns the final result. If
# there are inconsistencies, the result will be 1.
# The highlightGrids method is from SmartScript.
if inconGridColor:
self.highlightGrids(
'Fcst', 'SnowAmt6hr', 'SFC', gridTR, inconGridColor)
self.highlightGrids(
'Fcst', 'QPF6hr', 'SFC', gridTR, inconGridColor)
# The createGrid method is from SmartScript
self.createGrid(
'Fcst', 'SnowAmt6hrQPF6hr', 'SCALAR',
consistMask, gridTR,
descriptiveName='SnowAmt6hrQPF6hrInconsistent',
minAllowedValue=0, maxAllowedValue=1, units='Good/Bad')
if tempGridColor:
self.highlightGrids(
'Fcst', 'SnowAmt6hrQPF6hr', 'SFC', gridTR,
tempGridColor)
self.inconsistent = True
# While not required, I like to terminate my methods with a return
# statement to make it clear this is where the method ends.
return
def _runSnowAmt6hrWxCheck(self, timeRange):
# This implements the check that if SnowAmt6hr >= 0.1, then the Wx grid
# must contain S, SW, or IP, regardless of whether or not there is
# any freezing or liquid types. Finally, the check does not look at
# anything other than the Wx type. In other words, the check will be
# okay if SnowAmt != 0 and Wx has Chc:S:- or Def:SW:-- or Lkly:S:+.
# If there are locks, post urgent messages and return from the method.
snowLockMe, wxLockMe, snowLockOther, wxLockOther = \
self.__checkLockedStatus(['SnowAmt6hr', 'Wx'])
if snowLockMe or wxLockMe or snowLockOther or wxLockOther:
# Something's locked, create messages.
self._makeLockMsgs(
snowLockMe, wxLockMe, snowLockOther, wxLockOther,
'SnowAmt6hr', 'Wx', 'snowLockMe', 'wxLockMe',
'snowLockOther', 'wxLockOther', 'SnowAmt6hr/Wx')
return
# Make sure there are actually SnowAmt6hr grids in the time range.
# The getGridInfo method is from SmartScript.
snowAmtInfoList = self.getGridInfo(
'Fcst', 'SnowAmt6hr', 'SFC', timeRange)
if snowAmtInfoList == []:
message = self._getMsg(
'noGrids', element='SnowAmt6hr', timeRange=timeRange,
method='SnowAmt6hr/Wx')
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
# I return instead of aborting because the user may have asked for
# other tests that do not have missing grid problems.
return
for snowAmtIndex, snowAmtGrid in enumerate(self.getGrids(
'Fcst', 'SnowAmt6hr', 'SFC', timeRange, mode='List', cache=0)):
gridTR = snowAmtInfoList[snowAmtIndex].gridTime()
# Make sure the snowAmtGrid meets the time constraints.
if not self.__checkTC('SnowAmt6hr', gridTR):
message = self._getMsg(
'badTC', element='SnowAmt6hr', timeRange=gridTR)
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# around is from SmartScript
snowAmtGrid = around(snowAmtGrid, 1)
# The greater_equal method is from Numeric.
# The getGridInfo method is from SmartScript.
nonZeroMask = greater_equal(snowAmtGrid, 0.1 - snowAmtTol)
wxInfoList = self.getGridInfo('Fcst', 'Wx', 'SFC', gridTR)
# Check for Wx grid in gridTR
if wxInfoList == []:
message = self._getMsg(
'noGrids', element='Wx', timeRange=gridTR,
method='SnowAmt6hr/Wx')
# The statusBarMsg method is from SmartScript
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# Now check the overlapping Wx grids. Initialize a totally
# inconsistent grid.
# ones is from Numeric
inconsistGrid = ones(nonZeroMask.shape, int)
for wxIndex, wxGrid in enumerate(self.getGrids(
'Fcst', 'Wx', 'SFC', gridTR, mode='List', cache=0)):
# The wxMask method is from SmartScript
sMask = self.wxMask(wxGrid, ':S:')
swMask = self.wxMask(wxGrid, ':SW:')
ipMask = self.wxMask(wxGrid, ':IP:')
# The logical_or method is from Numeric. For the two input
# arrays, if both values are "False", then the result is
# "False". Otherwise, the result is "True".
snowMask = logical_or(logical_or(sMask, swMask), ipMask)
# I don't need these arrays any longer. Delete them to free
# up the memory they use.
del (sMask, swMask, ipMask)
# The where method is from Numeric
wxMask = logical_not(snowMask)
# "Truth" table for the logical comparison follows
# SnowAmt6hr >= 0.1, 1; SnowAmt6hr < 0.1, 0
# Wx has S, SW, or IP, 0; Wx doesn't have S, SW, or IP, 1
# SnowAmt6hr >= 0.1 (1) and Wx has (0) = 0 (Good result)
# SnowAmt6hr >= 0.1 (1) and Wx doesn't have (1) = 1 (Bad result)
# SnowAmt6hr < 0.1 (0) and Wx has (0) = 0 (Good result)
# SnowAmt6hr < 0.1 (0) and Wx doesn't have (1) = 0 (Good result)
#
# The logical_and, where, sometrue, and ravel methods are all
# from Numeric.
consistMask = logical_and(nonZeroMask, wxMask)
consistMask[logical_not(self.cwaMask)] = 0
# Update inconsistGrid to be the current state of the
# inconsistencies.
inconsistGrid = logical_and(inconsistGrid, consistMask)
if not sometrue(ravel(inconsistGrid)):
# There were no longer any inconsistencies between
# SnowAmt6hr and Wx.
break
else:
# This block will only execute if the for loop runs to
# completion, i.e., the break statement is not executed.
# So, if we get here, we have inconsistencies and need to
# highlight the appropriate grids.
# The highlightGrids method is from SmartScript.
if inconGridColor:
self.highlightGrids(
'Fcst', 'SnowAmt6hr', 'SFC', gridTR, inconGridColor)
self.highlightGrids(
'Fcst', 'Wx', 'SFC', gridTR, inconGridColor)
# The createGrid method is from SmartScript
self.createGrid(
'Fcst', 'SnowAmt6hrWx', 'SCALAR', inconsistGrid, gridTR,
descriptiveName='SnowAmt6hrWxInconsistent',
minAllowedValue=0, maxAllowedValue=1, units='Good/Bad')
if tempGridColor:
self.highlightGrids(
'Fcst', 'SnowAmt6hrWx', 'SFC', gridTR,
tempGridColor)
self.inconsistent = True
return
def _runQPF6hrPoPCheck(self, timeRange):
# This method implements the check that if any QPF6hr grid is non zero
# then one of the corresponding floating PoP grids must also be non
# zero.
# If there are locks, post urgent messages and return from the method.
qpfLockMe, popLockMe, qpfLockOther, popLockOther = \
self.__checkLockedStatus(['QPF6hr', 'PoP'])
if qpfLockMe or popLockMe or qpfLockOther or popLockOther:
# Something's locked, create messages.
self._makeLockMsgs(
qpfLockMe, popLockMe, qpfLockOther, popLockOther,
'QPF6hr', 'PoP', 'qpfLockMe', 'popLockMe',
'qpfLockOther', 'popLockOther', 'QPF6hr/PoP')
return
# The getGridInfo method is from SmartScript.
qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', timeRange)
# Make sure there are actually QPF6hr grids in the time range.
if qpfInfoList == []:
message = self._getMsg(
'noGrids', element='QPF6hr', timeRange=timeRange,
method='QPF6hr/PoP')
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
# I return instead of aborting because the user may have asked for
# other tests that do not have missing grid problems.
return
for qpfIndex, qpfGrid in enumerate(self.getGrids(
'Fcst', 'QPF6hr', 'SFC', timeRange, mode='List', cache=0)):
gridTR = qpfInfoList[qpfIndex].gridTime()
# Check the QPF6hr grid time constraints
if not self.__checkTC('QPF6hr', gridTR):
message = self._getMsg(
'badTC', element='QPF6hr', timeRange=gridTR)
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# around is from SmartScript
qpfGrid = around(qpfGrid, 2)
# The greater_equal method is from Numeric. The getGrids method
# is from SmartScript.
qpfNonZeroMask = greater_equal(qpfGrid, 0.01 - qpfTol)
popGrid = self.getGrids(
'Fcst', 'PoP', 'SFC', gridTR, mode='Max', noDataError=0,
cache=0)
# Since I don't need to loop over the PoP grids, just get their
# max, I don't need to call getGridInfo like in other methods.
# With noDataError=0 in the getGrids call, if there are no grids,
# then the special Python value None will be returned. So, I can
# just check that to see if all the PoP grids are missing. If
# there were a gap in the PoP grids, that would not be caught.
# But, no one's PoP grids should ever have a gap in them, right?
if popGrid is None:
message = self._getMsg(
'noGrids', element='PoP', timeRange=gridTR,
method='QPF6hr/PoP')
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# The equal method is from Numeric.
popZeroMask = equal(popGrid, 0)
# popZeroMask = 1 if PoP = 0; popZeroMask = 0 if PoP != 0
# qpfNonZeroMask = 1 if QPF6hr > 0; qpfNonZeroMask = 0 if QPF6hr = 0
# PoP = 0 (1) and QPF6hr = 0 (0) => 0 (Good result)
# PoP != 0 (0) and QPF6hr = 0 (0) => 0 (Good result)
# PoP != 0 (0) and QPF6hr > 0 (1) => 0 (Good result)
# PoP = 0 (1) and QPF6hr > 0 (1) => 1 (Bad result)
#
# The logical_and, where, sometrue, and ravel methods are all
# from Numeric.
consistMask = logical_and(qpfNonZeroMask, popZeroMask)
consistMask[logical_not(self.cwaMask)] = 0
if sometrue(ravel(consistMask)):
# The good result is if the logical_and returns zeros
# for every grid point, that is "none true". So, if
# the sometrue method evaluates True, there are
# inconsistencies.
# The createGrid and highlightGrids methods are from
# SmartScript.
self.createGrid(
'Fcst', 'QPF6hrPoP', 'SCALAR',
consistMask, gridTR,
descriptiveName='QPF6hrPoPInconsistent',
minAllowedValue=0, maxAllowedValue=1, units='Good/Bad')
if tempGridColor:
self.highlightGrids(
'Fcst', 'QPF6hrPoP', 'SFC', gridTR,
tempGridColor)
if inconGridColor:
self.highlightGrids(
'Fcst', 'QPF6hr', 'SFC', gridTR, inconGridColor)
self.highlightGrids(
'Fcst', 'PoP', 'SFC', gridTR, inconGridColor)
self.inconsistent = True
return
def _runQPF6hrWxCheck(self, timeRange):
# This method implements the check that if QPF6hr non zero, then the
# corresponding Wx grids must contain a precipitable Wx type. Note:
# the method only checks the Wx type, no cov/prob, no inten, etc.
# If there are locks, post urgent messages and return from the method.
qpfLockMe, wxLockMe, qpfLockOther, wxLockOther = \
self.__checkLockedStatus(['QPF6hr', 'Wx'])
if qpfLockMe or wxLockMe or qpfLockOther or wxLockOther:
# Something's locked, create messages.
self._makeLockMsgs(
qpfLockMe, wxLockMe, qpfLockOther, wxLockOther,
'QPF6hr', 'Wx', 'qpfLockMe', 'wxLockMe',
'qpfLockOther', 'wxLockOther', 'QPF6hr/Wx')
return
# The getGridInfo method is from SmartScript.
qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', timeRange)
# Make sure there are actually QPF6hr grids in the time range.
if qpfInfoList == []:
message = self._getMsg(
'noGrids', element='QPF6hr', timeRange=timeRange,
method='QPF6hr/Wx')
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
# I return instead of aborting because the user may have asked for
# other tests that do not have missing grid problems.
return
for qpfIndex, qpfGrid in enumerate(self.getGrids(
'Fcst', 'QPF6hr', 'SFC', timeRange, mode='List', noDataError=0,
cache=0)):
gridTR = qpfInfoList[qpfIndex].gridTime()
# Make sure the QPF6hr grid meets the time constraints
if not self.__checkTC('QPF6hr', gridTR):
message = self._getMsg(
'badTC', element='QPF6hr', timeRange=gridTR)
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# around is from SmartScript
qpfGrid = around(qpfGrid, 2)
# The greater_equal method is from Numeric.
qpfNonZeroMask = greater_equal(qpfGrid, 0.01 - qpfTol)
# The getGridInfo method is from SmartScript.
wxInfoList = self.getGridInfo('Fcst', 'Wx', 'SFC', gridTR)
# Make sure there are Wx grids overlapping the QPF6hr grid
if wxInfoList == []:
message = self._getMsg(
'noGrids', element='Wx', timeRange=gridTR,
method='QPF6hr/Wx')
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# Initialize a totally inconsistent grid and loop over the
# overlapping Wx grids.
inconsistGrid = ones(qpfNonZeroMask.shape, int)
for wxIndex, wxGrid in enumerate(self.getGrids(
'Fcst', 'Wx', 'SFC', gridTR, mode='List', noDataError=0,
cache=0)):
# The wxMask method is from SmartScript.
sMask = self.wxMask(wxGrid, ':S:')
swMask = self.wxMask(wxGrid, ':SW:')
ipMask = self.wxMask(wxGrid, ':IP:')
# The logical_or method is from Numeric.
snowMask = logical_or(logical_or(sMask, swMask), ipMask)
# I don't need these three grids any longer, so delete them
# and free up their memory.
del (sMask, swMask, ipMask)
rMask = self.wxMask(wxGrid, ':R:')
rwMask = self.wxMask(wxGrid, ':RW:')
lMask = self.wxMask(wxGrid, ':L:')
zlMask = self.wxMask(wxGrid, ':ZL:')
zrMask = self.wxMask(wxGrid, ':ZR:')
# The logical_or method is from Numeric.
rainMask = logical_or(
rMask, logical_or(
rwMask, logical_or(
lMask, logical_or(zlMask, zrMask))))
# Again, I don't need these grids any longer, so delete them
# and free up their memory.
del (rMask, rwMask, lMask, zlMask, zrMask)
precipMask = logical_or(snowMask, rainMask)
del (snowMask, rainMask)
wxMask = logical_not(precipMask)
# QPF6hr >= 0.01, 1; QPF6hr < 0.01, 0
# Wx has precip, 0; Wx doesn't have precip, 1
# QPF6hr >= 0.01 (1) and Wx has (0) = 0 (Good result)
# QPF6hr >= 0.01 (1) and Wx doesn't have (1) = 1 (Bad result)
# QPF6hr < 0.01 (0) and Wx has (0) = 0 (Good result)
# QPF6hr < 0.01 (0) and Wx doesn't have (1) = 0 (Good result)
#
# The logical_and, where, sometrue, and ravel methods are all
# from Numeric.
consistMask = logical_and(qpfNonZeroMask, wxMask)
consistMask[logical_not(self.cwaMask)] = 0
# Update the inconsistGrid to the current state of the
# inconsistencies.
inconsistGrid = logical_and(inconsistGrid, consistMask)
if not sometrue(ravel(inconsistGrid)):
# There were no longer any inconsistencies between the Wx
# grids and the QPF6hr grid.
break
else:
# This block will only execute if the for loop runs to
# completion, i.e., the break statement is not executed.
# So, if we get here, we have inconsistencies and need to
# highlight the appropriate grids.
# The highlightGrids method is from SmartScript.
if inconGridColor:
self.highlightGrids(
'Fcst', 'QPF6hr', 'SFC', gridTR, inconGridColor)
self.highlightGrids(
'Fcst', 'Wx', 'SFC', gridTR, inconGridColor)
# The createGrid method is from SmartScript.
self.createGrid(
'Fcst', 'QPF6hrWx', 'SCALAR', inconsistGrid, gridTR,
descriptiveName='QPF6hrWxInconsistent',
minAllowedValue=0, maxAllowedValue=1, units='Good/Bad')
if tempGridColor:
self.highlightGrids(
'Fcst', 'QPF6hrWx', 'SFC', gridTR,
tempGridColor)
self.inconsistent = True
return
def _runPoP12hrQPF6hrCheck(self, timeRange):
# This method implements the check that if any PoP12hr grid
# is >= 50%, then at least one of the two corresponding QPF6hr grids
# must be non zero.
# If there are locks, post urgent messages and return from the method.
qpfLockMe, popLockMe, qpfLockOther, popLockOther = \
self.__checkLockedStatus(['QPF6hr', 'PoP12hr'])
if qpfLockMe or popLockMe or qpfLockOther or popLockOther:
# Something's locked, create messages.
self._makeLockMsgs(
qpfLockMe, popLockMe, qpfLockOther, popLockOther,
'QPF6hr', 'PoP12hr', 'qpfLockMe', 'popLockMe',
'qpfLockOther', 'popLockOther', 'PoP12hr/QPF6hr')
return
# The getGridInfo method is from SmartScript.
# Make sure there are actually PoP12hr grids in the time range
popInfoList = self.getGridInfo('Fcst', 'PoP12hr', 'SFC', timeRange)
if popInfoList == []:
message = self._getMsg(
'noGrids', element='PoP12hr', timeRange=timeRange,
method='PoP12hr/QPF6hr')
# The statusBarMsg method is from SmartScript.
self.statusBarMsg(message, self.__getMsgSeverity('U'))
# I return instead of aborting because the user may have asked for
# other tests that do not have missing grid problems.
return
# This is the one check where it will almost always be the case that
# the "controlling" element (PoP12hr) will extend later in time than
# the "checked" element (QPF6hr). We don't want a lot of annoying
# pop-up messages for missing QPF6hr grids for that case. I will
# determine the end time of the last QPF6hr grid, adjust it back to the
# nearest 00 or 12 UTC time to align it with the PoP12hr grid, and then
# check the end time of the last PoP12hr grid. If the end time for
# the QPF6hr grid is earlier, I will adjust the timeRange variable
# inside this method to end with the QPF6hr grids.
# The getGridInfo method is from SmartScript.
qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', timeRange)
# Make sure there are actually QPF6hr grids in the time range
if qpfInfoList == []:
message = self._getMsg(
'noGrids', element='QPF6hr', timeRange=timeRange,
method='PoP12hr/QPF6hr')
self.statusBarMsg(message, self.__getMsgSeverity('U'))
# I return instead of aborting because the user may have asked for
# other tests that do not have missing grid problems.
return
lastQPFTR = qpfInfoList[-1].gridTime()
qpfEndTime = lastQPFTR.endTime().unixTime()
qpfEndHr = lastQPFTR.endTime().hour
qpfEndTime -= ((qpfEndHr % 12) * 3600)
popEndTime = popInfoList[-1].gridTime().endTime().unixTime()
if popEndTime > qpfEndTime:
# Adjust time range to QPF6hr time range
qpfStartTime = qpfInfoList[0].gridTime().startTime().unixTime()
qpfDuration = (qpfEndTime - qpfStartTime) // 3600
offset = (qpfStartTime - \
self.timeRange0_1.startTime().unixTime()) // 3600
timeRange = self.createTimeRange(
offset, offset+qpfDuration, 'Zulu')
message = self._getMsg(
'changeTR', method='PoP12hr/QPF6hr', timeRange=timeRange)
self.statusBarMsg(message, 'R')
# Because the timeRange has changed, popInfoList needs to be
# updated. qpfInfoList will be updated later.
# The getGridInfo method is from SmartScript.
popInfoList = self.getGridInfo('Fcst', 'PoP12hr', 'SFC', timeRange)
# Now, it's possible there were gaps in the PoP12hr grids and the
# new time range spans a gap. So, we have to check for grid
# existence again.
if popInfoList == []:
message = self._getMsg(
'noGrids', element='PoP12hr', timeRange=timeRange,
method='PoP12hr/QPF6hr')
self.statusBarMsg(message, self.__getMsgSeverity('U'))
return
for popIndex, popGrid in enumerate(self.getGrids(
'Fcst', 'PoP12hr', 'SFC', timeRange, mode='List', cache=0)):
gridTR = popInfoList[popIndex].gridTime()
qpfInfoList = self.getGridInfo('Fcst', 'QPF6hr', 'SFC', gridTR)
# Check for existence of QPF6hr grids in the time range.
if qpfInfoList == []:
message = self._getMsg(
'noGrids', element='QPF6hr', timeRange=gridTR,
method='PoP12hr/QPF6hr')
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# Check the PoP12hr time constraints
if not self.__checkTC('PoP12hr', gridTR):
message = self._getMsg(
'badTC', element='PoP12hr', timeRange=gridTR)
self.statusBarMsg(message, self.__getMsgSeverity('U'))
continue
# The greater_equal method is from Numeric.
pop50Mask = greater_equal(popGrid, 50)
# Initialize a totally inconsistent grid.
inconsistGrid = ones(pop50Mask.shape, int)
for qpfIndex, qpfGrid in enumerate(self.getGrids(
'Fcst', 'QPF6hr', 'SFC', gridTR, mode='List', cache=0)):
qpfGridTR = qpfInfoList[qpfIndex].gridTime()
# Check the QPF6hr time contraints
if not self.__checkTC('QPF6hr', qpfGridTR):
message = self._getMsg(
'badTC', element='QPF6hr', timeRange=qpfGridTR)
self.statusBarMsg(message, self.__getMsgSeverity('U'))
# If one of the QPF6hr grids has a bad time constraint,
# then I only have one QPF6hr grid. Time to break.
break
# around is from SmartScript
qpfGrid = around(qpfGrid, 2)
# The less method is from Numeric.
qpfMask = less(qpfGrid, 0.01 - qpfTol)
# The following is the "truth" table for the logical
# comparison.
# PoP12hr >= 50, 1; PoP12hr < 50, 0
# QPF6hr < 0.01, 1; QPF6hr >= 0.01, 0
# PoP12hr >= 50 (1) and QPF6hr < 0.01 (1) = 1 (Bad result)
# PoP12hr >= 50 (1) and QPF6hr >= 0.01 (0) = 0 (Good result)
# PoP12hr < 50 (0) and QPF6hr < 0.01 (1) = 0 (Good result)
# PoP12hr < 50 (0) and QPF6hr >= 0.01 (0) = 0 (Good result)
# logical_and is from Numeric
# The logical_and, where, sometrue, and ravel methods are all
# from Numeric.
consistMask = logical_and(pop50Mask, qpfMask)
consistMask[logical_not(self.cwaMask)] = 0
# Update the inconsistentGrid with the state of the
# inconsistencies.
inconsistGrid = logical_and(inconsistGrid, consistMask)
# ravel and sometrue are from Numeric
if not sometrue(ravel(inconsistGrid)):
# There were no longer any inconsistencies between the
# QPF6hr grids and PoP12hr grid.
break
else:
# This else block will only execute if the for loop exits
# "naturally", i.e., the above break statement didn't execute.
# This means there were inconsistencies.
# The highlightGrids method is from SmartScript.
## lin_index = nonzero(ravel(inconsistGrid))
## sh = list(shape(inconsistGrid))
## sh.reverse()
## new_index = zeros((len(lin_index), len(sh)))
## mod = zeros(len(lin_index))
## for j in arange(len(lin_index)):
## count = len(sh)
## for i in sh:
## lin_index[j], mod[j] = divmod(lin_index[j], i)
## count = count - 1
## new_index[j, count] = mod[j]
## print new_index
## print popGrid[0,0], qpfGrid[0,0]
if inconGridColor:
self.highlightGrids(
'Fcst', 'PoP12hr', 'SFC', gridTR, inconGridColor)
self.highlightGrids(
'Fcst', 'QPF6hr', 'SFC', gridTR, inconGridColor)
self.createGrid(
'Fcst', 'PoP12hrQPF6hr', 'SCALAR', inconsistGrid, gridTR,
descriptiveName='PoP12hrQPF6hrInconsistent',
minAllowedValue=0, maxAllowedValue=1, units='Good/Bad')
if tempGridColor:
self.highlightGrids(
'Fcst', 'PoP12hrQPF6hr', 'SFC', gridTR,
tempGridColor)
self.inconsistent = True
return
def _makeLockMsgs(
self, lockMe1, lockMe2, lockOther1, lockOther2, element1, element2,
lockMeKey1, lockMeKey2, lockOtherKey1, lockOtherKey2, method):
# As I went through the five check methods, I noted that this code
# was basically being repeated over and over again. So, here's a
# case where I took the repeated code, made it abstract, and turned
# it into a callable method. Now where each of the five methods had
# about 20 lines of code to do this, they now have only 5, which is
# a fairly substantial decrease. The trade-off is in somewhat
# lessened code readability because of having to jump from method
# to method to track the code.
# Below, I assign the call to _getMsg to a temporary variable
# called message for readability. Embedding the call to _getMsg
# in the call to statusBarMsg makes those calls much harder to
# follow.
# The statusBarMsg method is from SmartScript.
if lockMe1:
message = self._getMsg(
lockMeKey1, method=method, element=element1)
self.statusBarMsg(
message, self.__getMsgSeverity('U'))
if lockMe2:
message = self._getMsg(
lockMeKey2, method=method, element=element2)
self.statusBarMsg(
message, self.__getMsgSeverity('U'))
if lockOther1:
message = self._getMsg(
lockOtherKey1, method=method, element=element1)
self.statusBarMsg(
message, self.__getMsgSeverity('U'))
if lockOther2:
message = self._getMsg(
lockOtherKey2, method=method, element=element2)
self.statusBarMsg(
message, self.__getMsgSeverity('U'))
return
def _msgDict(self):
# Since I seem to be incapable of writing concise error messages,
# I decided to capture the error messages in a separate method.
# Because I tend to be verbose, I like to use triple quoted strings
# for messages since this helps minimize (not eliminate) characters
# which extend beyond the right margin of the editor window. But triple
# quoted strings disrupt the indentation patterns of the code, making
# the code harder to read. By capturing all the triple quoted strings
# in a separate method, the indentation issue is mitigated. Doing this
# does add some complexity to the code, but it's a fair trade-off in my
# mind. This method is just a dictionary of all the error messages,
# which may contain string formatting code place holders. Another
# method, _getMsg, will look up the message boiler plates here and have
# the logic to correctly pass the needed variables to the string
# formatting codes.
return {
'complete': 'NDFD_QPF_Checks complete.',
'0_240':
'''The NDFD_QPF_Checks procedure did not run over the 0 to 240 hour time period,
it ran over %s. This may be what you desired.''',
'cwaMask':
'''The procedure was not able to use the CWA edit area, %s,
provided in the configuration. You should inform the person responsible for procedures of
this problem. The procedure ran over the whole domain.''',
'incon': 'NDFD_QPF_Checks complete. Inconsistencies found!',
'snowLockMe':
'''You have the %s grid locked. Please save the %s grid. The %s
check was not run.''',
'qpfLockMe':
'''You have the %s grid locked. Please save the %s grid. The %s check was
not run.''',
'snowLockOther':
'''Another user has the %s grid locked. Please have that user save the %s grid. The
%s check was not run.''',
'qpfLockOther':
'''Another user has the %s grid locked. Please have that user save the %s grid. The
%s check was not run.''',
'wxLockMe':
'''You have the %s grid locked. Please save the %s grid. The %s check was not run.''',
'wxLockOther':
'''Another user has the %s grid locked. Please have that user save the %s grid. The %s
check was not run.''',
'popLockMe':
'''You have the %s grid locked. Please save the %s grid. The %s check was
not run.''',
'popLockOther':
'''Another user has the %s grid locked. Please have that user save the %s grid. The
%s check was not run.''',
'noGrids':
'''There are no %s grids in the time range, %s.
The %s Check skipped the time range.''',
'changeTR':
'''The time range of the %s check was changed to ensure the PoP12hr grid is not checked
beyond the time of the last QPF6hr grid. The time range used was %s.''',
'badTC':
'''A %s grid has the following time range: %s,
which does not adhere to the time constraint requirement. This %s grid has not been consistency checked at all.
Please fix the grid and re-run the procedure.''',
}
def _getMsg(self, key, timeRange=None, method=None, element=None):
# This method looks up the needed error message by passing key to
# _msgDict. The other parameters, if provided, are used to expand
# the embedded string formatting codes. The resulting message is
# passed back to the caller.
message = self._msgDict().get(key, '')
if key == '0_240':
message = message % str(timeRange)
return message
if key == 'cwaMask':
message = message % cwaEditArea
return message
if key == 'incon':
if inconGridColor and tempGridColor:
message = '%s Inconsistent grids highlighted %s.\nTemporary grids highlighted %s.' % (message, inconGridColor, tempGridColor)
return message
if inconGridColor:
message = '%s Inconsistent grids highlighted %s.' % (
message, inconGridColor)
return message
if tempGridColor:
message = '%s Temporary grids highlighted %s.' % (
message, tempGridColor)
return message
return message
if key == 'snowLockMe' or key == 'qpfLockMe' or key == 'wxLockMe' or \
key == 'snowLockOther' or key == 'qpfLockOther' or \
key == 'wxLockOther' or key == 'popLockMe' or \
key == 'popLockOther':
message = message % (element, element, method)
return message
if key == 'noGrids':
message = message % (element, str(timeRange), method)
return message
if key == 'changeTR':
message = message % (method, str(timeRange))
return message
if key == 'badTC':
message = message % (element, str(timeRange), element)
return message
# If for some reason the key look-up failed, then the message
# variable will be the empty string, '', and this will be returned.
# Since the calling method expects a string to be returned, I must
# ensure that this happens.
return message
def execute(self, timeRange, varDict):
# Are we in quiet mode? The variableList above does NOT have a
# variable for 'Quiet', by design. When run interactively, some error
# conditions will generate pop-up messages. If an office decides to
# run this procedure as part of an over-arching check procedure, they
# can choose to turn the pop-up messages into routine messages by
# passing in a varDict with a 'Quiet' key that evaluates to 'True'.
# In Python, you can use the 'get' method on a dictionary to test
# for the existence of a key. If the key exists, then 'get' returns
# the value. If the key doesn't exist, then the second argument of
# the 'get' call is returned. If you don't provide a second argument
# to the 'get' call and the key is not found, then None is returned.
# As you can see below, my call to 'get' will return False if the key
# 'Quiet' is not in varDict. The 'get' method allows you to avoid
# constructs like:
# try:
# self._quiet = varDict['Quiet']
# except KeyError:
# self._quiet = False
# I don't know for sure, but I'd be willing to bet that the 'get'
# method is just a wrapper to the 'try/except' construct very
# similar to the one demonstrated.
self._quiet = varDict.get('Quiet', False)
## self._quiet = True
# Make sure the configuration values are the correct types.
self.__checkConfigValueTypes()
# createTimeRange is from SmartScript
timeRange0_240 = self.createTimeRange(0, 240, 'Zulu')
checkCleanup = varDict.get('Check_Cleanup', 'Check')
self.__cleanup(timeRange0_240)
if checkCleanup == 'Cleanup':
self.statusBarMsg(self._getMsg('complete'), 'R')
self.cancel()
elementList = (
'PoP', 'QPF', 'Wx', 'SnowAmt', 'QPF6hr', 'SnowAmt6hr', 'PoP12hr')
for element in elementList:
self.loadParm('Fcst', element, 'SFC')
if timeRange.endTime().unixTime() - timeRange.startTime().unixTime() < \
3600: # No time range selected, use 0 to 240 hour range
timeRange = timeRange0_240
# If the user has a time range swept out, send an informational
# message.
if (timeRange.startTime().unixTime() != \
timeRange0_240.startTime().unixTime()) or \
(timeRange.endTime().unixTime() != \
timeRange0_240.endTime().unixTime()) or \
(timeRange.duration() != timeRange0_240.duration()):
# What the incredibly dense expression in the above if statement
# does is compare the start and end of the time range to the start
# and end of the 0-240 hour time range. If either are different,
# then a time range was swept out.
self.statusBarMsg(
self._getMsg(
'0_240', timeRange=timeRange), self.__getMsgSeverity('S'))
# I'll need to know the unix time of 00Z so I can determine the
# start time of temporary grids later. I'll need this in more than
# one of the methods called later, so this will become an instance
# variable, i.e., prefixed with "self." I also need an instance
# variable that flags whether or not there were inconsistent grids.
self.timeRange0_1 = self.createTimeRange(0, 1, 'Zulu')
self.inconsistent = False
# A CWA edit area can be provided in the configuration section.
# Attempt to encode that edit area as a Numeric Python mask so that
# the later checks are limited to the edit area. The GFE is not very
# friendly if the encoding fails. The GFE will send a nasty message
# to the user, but continue executing the procedure. No trappable
# error is thrown. As of this writing, the GFE appears to create an
# array of shape (0, 0) if the encoding cannot be done, so I will
# check for that and, if I find it, then set the edit area to the
# domain.
# encodeEditArea comes from SmartScript. For the points that are in
# the edit area, a value of one is assigned. Otherwise, a value of
# zero is assigned.
if cwaEditArea:
self.cwaMask = self.encodeEditArea(cwaEditArea)
if self.cwaMask.shape == (0, 0):
# Use the getGridInfo command to get information about the
# SnowAmt grid. From this, the grid size can be extracted. I
# could use getGridInfo on any valid GFE grid.
# getGridInfo is from SmartScript
snowAmtInfoList = self.getGridInfo(
'Fcst', 'SnowAmt6hr', 'SFC', timeRange)
# I painfully discovered that the array shape is (y, x)
gridSize = (snowAmtInfoList[0].gridLocation().gridSize().y,
snowAmtInfoList[0].gridLocation().gridSize().x)
# ones is from Numeric. It creates an array of the given size
# and data type where all values are one.
self.cwaMask = ones(gridSize, int)
self.statusBarMsg(
self._getMsg('cwaMask'), self.__getMsgSeverity('S'))
else:
snowAmtInfoList = self.getGridInfo(
'Fcst', 'SnowAmt6hr', 'SFC', timeRange)
gridSize = (snowAmtInfoList[0].gridLocation().gridSize().y,
snowAmtInfoList[0].gridLocation().gridSize().x)
self.cwaMask = ones(gridSize, int)
# Based on the user's input, run the appropriate checks.
# By making each of these options a checkbox with only one option in
# the VariableList above, if an option is unchecked then an empty
# list, [], will be what's in varDict. If an option is checked then a
# list with the value "Yes", ["Yes"], will be what's in varDict. In
# Python, a conditional expression can be whether or not a data
# structure is empty. In these cases, an empty data structure,
# e.g., an empty list, an empty tuple, an empty dictionary,
# conditionally test to False while non empty data structures
# conditionally test to True. In the if statements below, every varDict
# lookup returns a list: either [] or ["Yes"]. I think the constructs
# below or more elegant and easier to understand.
if varDict['Run SnowAmt6hr/QPF6hr Check?']:
# Call the SnowAmt6hr/QPF6hr check method
self._runSnowAmt6hrQPF6hrCheck(timeRange)
if varDict['Run SnowAmt6hr/Wx Check?']:
# Call the SnowAmt6hr/Wx check method
self._runSnowAmt6hrWxCheck(timeRange)
if varDict['Run QPF6hr/PoP Check?']:
# Call the QPF6hr/PoP check method
self._runQPF6hrPoPCheck(timeRange)
if varDict['Run QPF6hr/Wx Check?']:
# Call the QPF6hr/Wx check method
self._runQPF6hrWxCheck(timeRange)
if varDict['Run PoP12hr/QPF6hr Check?']:
# Call the PoP12hr/QPF6hr check method
self._runPoP12hrQPF6hrCheck(timeRange)
if self.inconsistent:
self.statusBarMsg(self._getMsg('incon'), self.__getMsgSeverity('S'))
else:
self.statusBarMsg(self._getMsg('complete'), 'R')