awips2/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/TextUtils.py
2022-05-05 12:34:50 -05:00

810 lines
31 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.
##
# ----------------------------------------------------------------------------
# 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.
#
# TextUtils.py
# Utility methods for Text Products.
#
# Author: hansen
# ----------------------------------------------------------------------------
##
# This is a base file that is not intended to be overridden.
##
from math import *
import types
import os, re, time
import WxMethods
import SiteInfo
from com.raytheon.uf.common.dataplugin.gfe.db.objects import ParmID
class TextUtils:
def __init__(self):
self.__percentCompleted = 0
def DAY(self):
return 6
def NIGHT(self):
return 18
def DAYTIME(self):
return 1
def NIGHTTIME(self):
return 0
def DAYNIGHT(self):
return -1
def dirList(self):
dirSpan = 45 # 45 degrees per direction
base = 22.5 # start with N
return [
('N', 360-base, 361),
('N', 0, base),
('NE',base , base + 1*dirSpan),
('E', base + 1*dirSpan, base + 2*dirSpan),
('SE',base + 2*dirSpan, base + 3*dirSpan),
('S', base + 3*dirSpan, base + 4*dirSpan),
('SW',base + 4*dirSpan, base + 5*dirSpan),
('W', base + 5*dirSpan, base + 6*dirSpan),
('NW',base + 6*dirSpan, base + 7*dirSpan)
]
def dir16PtList(self):
dirSpan = 22.5 # 22.5 degrees per direction
base = 11.25 # start with N
return [
('N', 360-base, 361),
('N', 0, base),
('NNE', base , base + 1*dirSpan),
('NE', base + 1*dirSpan, base + 2*dirSpan),
('ENE', base + 2*dirSpan, base + 3*dirSpan),
('E', base + 3*dirSpan, base + 4*dirSpan),
('ESE', base + 4*dirSpan, base + 5*dirSpan),
('SE', base + 5*dirSpan, base + 6*dirSpan),
('SSE', base + 6*dirSpan, base + 7*dirSpan),
('S', base + 7*dirSpan, base + 8*dirSpan),
('SSW', base + 8*dirSpan, base + 9*dirSpan),
('SW', base + 9*dirSpan, base + 10*dirSpan),
('WSW', base + 10*dirSpan, base + 11*dirSpan),
('W', base + 11*dirSpan, base + 12*dirSpan),
('WNW', base + 12*dirSpan, base + 13*dirSpan),
('NW', base + 13*dirSpan, base + 14*dirSpan),
('NNW', base + 14*dirSpan, base + 15*dirSpan),
]
# Dictionary for converting Wind Direction from letters to degrees
def dirList2(self):
return {
'N' : 0,
'NE':45,
'E' :90,
'SE':135,
'S' :180,
'SW':225,
'W' :270,
'NW':315,
}
def dirToText(self, numDir):
"Convert the numerical direction to a string: N, NE, E, ..."
for dirInfo in self.dirList():
if numDir >= dirInfo[1] and numDir < dirInfo[2]:
return dirInfo[0]
print("WARNING -- illegal direction for conversion: ", numDir)
return None
def dirTo16PtText(self, numDir):
"Convert the numerical direction to a string: N, NE, E, ..."
for dirInfo in self.dir16PtList():
if numDir >= dirInfo[1] and numDir < dirInfo[2]:
return dirInfo[0]
print("WARNING -- illegal direction for conversion: ", numDir)
return None
def vector_dir(self, direction):
if not isinstance(direction, str):
direction = self.dirToText(direction)
direction = direction.replace("N", "north")
direction = direction.replace("S", "south")
direction = direction.replace("E", "east")
direction = direction.replace("W", "west")
return direction
def getVis(self, subkeyList, outputFormat="NM"):
# Find the "lowest" visibility specified in the subkeys
conversionDict = self.visibilityConversionDict()
resultVisNM = None
resultVis = "<NoVis>"
for subkey in subkeyList:
vis = subkey.visibility()
if vis == "<NoVis>":
continue
if resultVisNM is None:
resultVisNM = conversionDict[vis]
resultVis = vis
else:
# Find lowest visibility
visNM = conversionDict[vis]
if visNM < resultVisNM:
resultVisNM = visNM
resultVis = vis
if outputFormat == "NM":
return resultVisNM
else:
return resultVis
def visibilityConversionDict(self):
# Conversion from text to nautical miles
return {
"0SM": 0,
"1/4SM": 0.2174,
"1/2SM": 0.4348,
"3/4SM": 0.6522,
"1SM": 0.8696,
"11/2SM": 1.304,
"2SM": 1.739,
"21/2SM": 2.174,
"3SM": 2.609,
"4SM": 3.478,
"5SM": 4.348,
"6SM": 5.217,
"P6SM": 6.087,
}
def findSubkeys(self, subkeys, searchKeys):
# Return 1 if any of the searchKeys are found in subkeys
wxSize = len(subkeys)
wxStr = ""
for x in range(wxSize):
wxStr += str(subkeys[x])
if x < wxSize - 1:
wxStr += '^'
wx = wxStr
for searchKey in searchKeys:
if WxMethods.WxContains(wx, searchKey):
return 1
return 0
########################################################################
# Methods for accessing customizable dictionaries and tables
# Dictionary access
def access_dictionary(self, tree, node, key, value, dictName, execMethods=1):
# Access the dictionary with the given name for the given key value
# The value for a key may be :
# a text string
# a method
# a dictionary. The dictionary may be of several forms:
# a non-linear value dictionary (nlValue). This
# dictionary has entries that are tuples with values
# or the keyword "default" with a value. (See the
# Text Product User Guide section on Non-linear Thresholds).
# a dictionary by weather element. In this case,
# the dictionary can have the optional entry "otherwise"
# to be used if the given element has not entry.
#
# If a method, it will be called with arguments:
# tree, node
# If there is no entry found, an empty string will be returned
#
dictionary = getattr(self, dictName)(tree, node)
#print dictionary
if key in dictionary:
entry = dictionary[key]
#print type(entry), entry
if execMethods and type(entry) is types.MethodType:
return entry(tree, node, key, value)
# For some reason, if a method is assigned within
# the Local class, it appears as a tuple instead of a
# method
if execMethods and type(entry) is tuple:
try:
return entry[0](tree, node, key, value)
except:
# In case it's really a tuple
return entry
elif type(entry) is dict:
# Check for nlValue dictionary
for key in entry:
if key == "default" or type(key) is tuple:
return entry
# Otherwise, look for value in dictionary
try:
return entry[value]
except:
# See if there is an "otherwise" entry
try:
return entry["otherwise"]
except:
return ""
else:
return entry
else:
if "otherwise" in dictionary:
return dictionary["otherwise"]
return ""
def calcTopoPercentage(self, tree, node, areaLabel, value):
# Calculate the percentage of topo points in the current edit area that are above
# the given value
parmHisto = tree.getTopoHisto(areaLabel)
totalPoints = parmHisto.numberOfGridPoints()
if totalPoints == 0:
return 0.0
countAbove = 0
for histSample in parmHisto.histoSamples():
for histPair in histSample.histogram():
if histPair.value().scalar() > value:
countAbove = countAbove + histPair.count()
return float(countAbove)/totalPoints * 100.0
def callMethod(self, value, method):
"Call the given method with the value"
if method is not None:
value = method(value)
return value
def fformat(self, value, roundVal):
# Return a string for the floating point value
# truncated to the resolution given by roundVal
if roundVal > 1.0:
return repr(int(value))
else:
# Determine how many decimal points
# e.g. if roundVal is .01, dec will be 2
val = roundVal
dec = 0
while val < 1:
val = val * 10
dec = dec + 1
dec = repr(dec)
numFormat = "%10."+dec+"f"
value = numFormat %value
value = value.strip()
return value
def convertDirection(self, numDir):
"Convert the numerical direction to a string: N, NE, E, ..."
for dirInfo in self.dirList():
if numDir >= dirInfo[1] and numDir < dirInfo[2]:
return dirInfo[0]
return "N" # this line for pychecker
def direction_movement(self, dir1, dir2):
# Returns -1, 0, or 1 if the change from dir1 to dir2 is
# counterclockwise, no change, or clockwise, respectively.
# Note differences of 180 degrees can return -1 or 1.
dirList2 = self.dirList2()
if type(dir1) is str:
dir1 = dirList2[dir1]
if type(dir2) is str:
dir2 = dirList2[dir2]
diff = dir2 - dir1
absDiff = abs(diff)
if diff == 0:
return 0
elif absDiff <= 180:
return diff / absDiff
else:
return -diff / absDiff
def direction_difference(self, dir1, dir2):
# Returns the difference dir2 - dir2. Values <0 or more than
# 180 are normalized so that this function always return values
# between 0 and 180.
dirList2 = self.dirList2()
if type(dir1) is str:
dir1 = dirList2[dir1]
if type(dir2) is str:
dir2 = dirList2[dir2]
diff = dir2 - dir1
absDiff = abs(diff)
if absDiff <= 180:
return absDiff
else:
return abs(absDiff - 360)
def direction_between(self, testdir, dir1, dir2):
# Returns 1 if dir is between dir1 and dir2, 0 otherwise
# Note if dir1 - dir2 == 180 this function always returns 1
if isinstance(testdir, str):
testdir = self.dirList2()[testdir]
totalDiff = self.direction_difference(dir1, dir2)
diff1 = self.direction_difference(testdir, dir1)
diff2 = self.direction_difference(testdir, dir2)
# if dir is inbetween the sum of the differences will be the same
if abs(diff1 + diff2 - totalDiff) < 0.1:
return 1
else:
return 0
def handleError(self, errorMsg, argDict):
ut = argDict["utility"]
ut.handleError(errorMsg)
#tkMessageBox.showwarning("Warning", errorMsg)
def round(self, val, mode, increment):
value = float(val)
if type(increment) is str:
return value
if not (mode == "RoundUp" or mode == "RoundDown" or mode == "Nearest"):
print(mode, "is an invalid mode.")
return value
# check for the case where no work is needed.
if value % increment == 0:
return value
sign = abs(value) / value
delta = 0
if mode == "RoundUp" and sign > 0:
delta = sign * increment
elif mode == "RoundDown" and sign < 0:
delta = sign * increment
if mode == "RoundUp":
value = (int(value / increment) * increment) + delta
elif mode == "RoundDown":
value = (int(value / increment) * increment) + delta
elif mode == "Nearest":
value = int((value + (sign * increment / 2.0)) / increment) * increment
return float(value)
def average(self,v1,v2):
return (v1+v2)/2.0
def vectorAverage(self, v1, v2):
# v1, v2 are (mag,dir) tuples
uw1, vw1 = self.MagDirToUV(v1[0], v1[1])
uw2, vw2 = self.MagDirToUV(v2[0], v2[1])
u = (uw1 + uw2) / 2.0
v = (vw1 + vw2) / 2.0
return self.UVToMagDir(u, v)
def MagDirToUV(self, mag, direction):
#Converts magnitude, direction to u, v
DEG_TO_RAD = 0.017453292
uw = sin(direction * DEG_TO_RAD) * mag
vw = cos(direction * DEG_TO_RAD) * mag
return (uw, vw)
def UVToMagDir(self, u, v):
# Converts u, v to magnitude, direction
RAD_TO_DEG = 57.296083
speed = sqrt(u * u + v * v)
direction = atan2(u, v) * RAD_TO_DEG
while direction < 0.0:
direction += 360.0
while direction >= 360.0:
direction -= 360.0
#print "Speed, dir ", speed, direction
return (speed, direction)
def setProgressPercentage(self, percentage):
self.__percentCompleted = percentage
def progressMessage(self, fraction, percent, message):
percent = int(fraction * percent)
self.__percentCompleted = int(self.__percentCompleted + percent)
print("Progress: " + repr(self.__percentCompleted) + "% " + message)
def getParmID(self, parmNameAndLevel, databaseID):
index = parmNameAndLevel.find("_")
if index == -1:
name = parmNameAndLevel
level = "SFC"
parm = ParmID(name,databaseID,level)
else:
name = parmNameAndLevel[0:index]
level = parmNameAndLevel[index+1:]
parm = ParmID(name,databaseID,level)
return parm
def nlValue(self, nlValue, lookupValue):
# Apply a non-linear value to the given value
# nlValue might be a dictionary to be applied to value
# OR it could be a simple constant
if isinstance(nlValue, dict):
for (key, value) in nlValue.items():
if not isinstance(key, str):
if lookupValue >= key[0] and lookupValue < key[1]:
return value
if 'default' in nlValue:
return nlValue['default']
msgString = """ILLEGAL NON-LINEAR THRESHOLD dictionary.
No dictionary entry for value: """ + repr(lookupValue) + """
Make sure your non-linear threshold dictionaries do not
have "gaps" in the ranges. For example, your dictionary
should look like this:
def maximum_range_nlValue_dict(self, tree, node):
### ConfigVariables
# Maximum range to be reported within a phrase
# e.g. 5 to 10 mph
# Units depend on the product
dict = TextRules.TextRules.maximum_range_nlValue_dict(self,tree, node)
dict["Wind"] = {
(0, 5) : 0,
(5, 13) : 5,
(13, 28) : 10,
"default" : 15,
}
return dict
NOT this:
def maximum_range_nlValue_dict(self, tree, node):
### ConfigVariables
# Maximum range to be reported within a phrase
# e.g. 5 to 10 mph
# Units depend on the product
dict = TextRules.TextRules.maximum_range_nlValue_dict(self,tree, node)
dict["Wind"] = {
(0, 4) : 0,
(5, 12) : 5,
(13, 27) : 10,
"default" : 15,
}
return dict
"""
raise ValueError(msgString)
elif type(nlValue) is types.MethodType:
return nlValue(lookupValue)
else:
# Constant value
return nlValue
def roundValue(self, value, roundingMethod, mode, increment_nlValue, maxFlag=0):
nlIncrement = self.nlValue(increment_nlValue, value)
if type(roundingMethod) is types.MethodType:
return roundingMethod(value, mode, nlIncrement, maxFlag)
else:
return self.round(value, mode, nlIncrement)
def getRangeInfo(self, tree, node, elementName):
rangeThreshold_nlValue = self.range_nlValue(tree, node, elementName, elementName)
rangeBias_nlValue = self.range_bias_nlValue(tree, node, elementName, elementName)
minRange_nlValue = self.minimum_range_nlValue(tree, node, elementName, elementName)
minBias_nlValue = self.minimum_range_bias_nlValue(tree, node, elementName, elementName)
maxRange_nlValue = self.maximum_range_nlValue(tree, node, elementName, elementName)
maxBias_nlValue = self.maximum_range_bias_nlValue(tree, node, elementName, elementName)
increment_nlValue = self.increment_nlValue(tree, node, elementName, elementName)
null_nlValue = self.null_nlValue(tree, node, elementName, elementName)
return self.RangeInfo(rangeThreshold_nlValue, rangeBias_nlValue, minRange_nlValue, minBias_nlValue,
maxRange_nlValue, maxBias_nlValue, increment_nlValue, null_nlValue)
def applyRanges(self, tree, node, minVal, maxVal, elementName):
rangeInfo = self.getRangeInfo(tree, node, elementName)
return self.applyRangeValues(tree, node, minVal, maxVal, elementName, rangeInfo)
def applyRangeValues(self, tree, node, minVal, maxVal, elementName, rangeInfo):
avg = self.average(float(minVal), maxVal)
diff = abs(maxVal - minVal)
# If the range is not great enough, return as a single value
if rangeInfo.rangeThreshold_nlValue != "":
threshold = self.nlValue(rangeInfo.rangeThreshold_nlValue, avg)
if diff < threshold:
bias = self.nlValue(rangeInfo.rangeBias_nlValue, avg)
if bias == "Average":
avg = self.roundStatistic(tree, node, avg, elementName)
return avg, avg
elif bias == "Max":
return maxVal, maxVal
else:
return minVal, minVal
# Apply minimum range
if rangeInfo.minRange_nlValue != "":
minRange = self.nlValue(rangeInfo.minRange_nlValue, avg)
if diff < minRange:
minVal, maxVal = self.applyBias(
tree, node, elementName, minVal, maxVal,
rangeInfo.minBias_nlValue, avg, minRange,
rangeInfo.increment_nlValue)
# Apply maximum range
if rangeInfo.maxRange_nlValue != "":
maxRange = self.nlValue(rangeInfo.maxRange_nlValue, avg)
if diff > maxRange:
minVal, maxVal = self.applyBias(
tree, node, elementName, minVal, maxVal,
rangeInfo.maxBias_nlValue, avg, maxRange,
rangeInfo.increment_nlValue)
# Cut-off at null_nlValue if max > null_nlValue and min < null_nlValue
threshold = self.nlValue(rangeInfo.null_nlValue, maxVal)
if minVal > 0 and maxVal >= threshold and minVal < threshold:
#print "cut-off", minVal, maxVal, threshold, elementName
roundingMethod = self.rounding_method(tree, node, elementName, elementName)
nlIncrement = self.nlValue(self.increment_nlValue(
tree, node, elementName, elementName), threshold)
minVal = self.roundValue(threshold, roundingMethod, "RoundUp", nlIncrement)
if minVal < threshold:
minVal = threshold
#print " new min", minVal
return minVal, maxVal
def applyBias(self, tree, node, elementName, minVal, maxVal, bias_nlValue,
avg, rangeValue, increment_nlValue):
bias = self.nlValue(bias_nlValue, avg)
#print "applying bias", minVal, maxVal, elementName, bias, rangeValue
if bias == "Average":
inc = rangeValue/2.0
minVal = self.roundStatistic(tree, node, avg - inc, elementName)
maxVal = self.roundStatistic(tree, node, avg + inc, elementName)
## If ranges are being applied to values that
## span zero, you can end up with a max-min being greater than
## rangeValue because Python rounds away from zero. (-0.5 rounds to
## -1.0, not 0.0.) The test below checks for this and adds
## 1 back to the min.
if maxVal-minVal > rangeValue:
minVal += 1
else:
increment = self.nlValue(increment_nlValue, avg)
rangeValue = self.round(rangeValue, "Nearest", increment)
minAllowedValue, maxAllowedValue = tree.library.getLimits(elementName)
if bias == "Max":
minVal = maxVal - rangeValue
else:
maxVal = minVal + rangeValue
if maxVal > maxAllowedValue:
maxVal = maxAllowedValue
if minVal < minAllowedValue:
minVal = minAllowedValue
#print " returning new", minVal, maxVal
return minVal, maxVal
#######################################################
# Statistics manipulation
def SCALAR(self):
return 0
def MAGNITUDE(self):
return 1
def DIRECTION(self):
return 2
def VECTOR(self):
return 3
def VECTOR_TEXT(self):
return 4
def VECTOR_NUM(self):
return 5
def WEATHER(self):
return 6
def DISCRETE(self):
return 7
def getValue(self, stats, method="Average", dataType=None):
# "stats" is either a single value or a tuple of 2 values
# method is any mergeMethod
if dataType == self.VECTOR():
mag, direction = stats
mag = self.getValue(mag, method)
return (mag, direction)
if isinstance(stats, tuple):
if method == "Max":
return stats[1]
elif method == "Min":
return stats[0]
elif method == "Sum":
return stats[0] + stats[1]
elif method == "MinMax":
return stats
else:
if stats[0] is None or stats[1] is None:
return None
return self.average(stats[0], stats[1])
else:
if method == "MinMax":
return (stats, stats)
else:
return stats
def makeSubkeyList(self, weatherKey):
# Make sure subkeyList is a true list
length = len(weatherKey)
newList = []
index = 0
for subkey in weatherKey:
newList.append(subkey)
index = index + 1
if index >= length:
break
return newList
def storeAWIPS(self, product, AWIPSkey="", host=None):
# Stores text in string "product" into
# the AWIPS text database via the given host if host is defined using
# ssh technique. Otherwise uses the AWIPS textdb command directly.
# Note: for the ssh mode, you need to have an entry in the .rhosts
# file of your home directory on lx1
#
if AWIPSkey == "":
return # do nothing
# use the command directly - assumes FXA environment setup
if host is None:
# (code adopted from Paul Jendrowski 9/18/03)
# set path to textdb command
cmd = "gfetextdb -w " + AWIPSkey
# issue the command
db = os.popen(cmd, 'w')
db.write(product)
db.close()
# use ssh (or rsh) to communicate with the textdb
else:
try:
command= "ssh " + host + " 'textdb -w " + AWIPSkey +"'"
except:
command= "rsh " + host + " 'textdb -w " + AWIPSkey +"'"
saveProduct = os.popen(command,'w')
saveProduct.write(product)
saveProduct.close()
def getPreviousProduct(self, productID, searchString="", version=0):
# gets a previous product from the AWIPS database
from com.raytheon.viz.gfe.core import DataManagerUIFactory
from com.raytheon.viz.gfe.product import TextDBUtil
# Redmine #17120 - return to pre-DR 15703 behavior.
opMode = DataManagerUIFactory.getCurrentInstance().getOpMode().name() == "OPERATIONAL"
previousProduct = TextDBUtil.retrieveProduct(productID, version, opMode)
previousProduct = previousProduct.strip()
if searchString != "":
# extract the specified section
section = re.sub(r'^[=, A-Za-z0-9\-\n\./]+' +
searchString + r'[=, A-Za-z0-9\-\n\/]*\.\.\.\n' +
r'*([=, A-Za-z0-9\-\n\./]+)\$\$[=, A-Za-z0-9\-\n'
+ r'\.\$@/]+', r'\1', previousProduct)
return section
else:
return previousProduct
def formatTimeString(self, gmTime, dateFormat, newTimeZone=None):
# converts the specified time (in seconds) to the specified time zone
# the time returned is in local time in units of seconds.
# newTimeZone must be an accepted time zone identifier such as
# "EST5EDT", CST6CDT", "MST7MDT", "PST8PST", "AST9ADT" "HST10"
myTimeZone = os.environ["TZ"] # save the defined time zone
if newTimeZone is None:
newTimeZone = myTimeZone
os.environ["TZ"] = newTimeZone # set the new time zone
time.tzset()
timeZoneStr = time.strftime(dateFormat, time.localtime(gmTime))
os.environ["TZ"] = myTimeZone # set the time zone back
time.tzset()
return timeZoneStr # return the time as a string
# Adopted from ER 8/04
def debug_print(self, msg="", trace=0, limit=10):
"""ZFP_ER_Overrides addition of ZFP_ER_Overrides._debug_print.
ER method for generic debug prints switched on/off by self._debug.
Automatically prints calling method's name, file, class and line
number plus an optional message string.
(e.g. self.debug_print('Debug message')
If the 'trace' flag is set to 1, the Python traceback info will not be
displayed. This is useful for displaying multiple formatted DEBUG
messages.
(e.g. self.debug_print('2nd Debug message', 1)
"""
# This method requires: import traceback
import traceback
# If debug is set up as a dictionary, then you could turn on
# method specific printing.
# Definition["debug"] = {"_myMeth1":1, "_myMeth2":0}
# Try to get traceback info - if debug flag is defined and not off
try:
if self._debug:
# Get debug info
file, lineno, name, text = traceback.extract_stack()[-2]
else:
return # bail now - turned off
except:
return # bail now - not defined
# Define counter to track number of times this method has been printed
count = 0
# See if debug counter dictionary has been defined
try:
if type(self._debugDict) == type({}):
pass
except:
self._debugDict = {}
# See if method-specific printing is being used
if type(self._debug) == type({}):
# If this method has a method specific flag - get it
if name in self._debug:
flag = self._debug[name]
else:
flag = 0 # not specified - don't display
else:
flag = self._debug
# If debug flag is turned off
if not flag:
return # bail out now
# Track number of times this method has been displayed
if name in self._debugDict:
count = self._debugDict[name] + 1
else:
count = 1
# If debug message limit is not reached
if count <= limit:
# If this is not a continuation debug message
if trace == 0:
# Record the printing of this message
self._debugDict[name] = count
# Print the traceback message
print("DEBUG: %s in %s at line %d" % (name, file, lineno))
print("DEBUG: Class = %s %d\n\n" % (self.__class__, count))
#print "Super classes:",self.__class__.__bases__
# If there is a message, print that too
if msg != "":
print('\t%s' % (msg))
class RangeInfo:
def __init__(self,rangeThreshold_nlValue, rangeBias_nlValue,
minRange_nlValue, minBias_nlValue,
maxRange_nlValue, maxBias_nlValue, increment_nlValue,
null_nlValue):
self.rangeThreshold_nlValue = rangeThreshold_nlValue
self.rangeBias_nlValue = rangeBias_nlValue
self.minRange_nlValue = minRange_nlValue
self.minBias_nlValue = minBias_nlValue
self.maxRange_nlValue = maxRange_nlValue
self.maxBias_nlValue = maxBias_nlValue
self.increment_nlValue = increment_nlValue
self.null_nlValue = null_nlValue
def getSiteInfo(self, infoType, siteID):
# Get information about an NWS site given the 3-letter site id
# infoType can be: "region", "wfoCity", "wfoCityState", "fullStationID
return SiteInfo.SiteInfo().getInfo(infoType, siteID)