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

1943 lines
81 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.
#
# WxPhrases.py
# Methods for producing text forecast from SampleAnalysis statistics.
#
# Author: hansen
# ----------------------------------------------------------------------------
# SOFTWARE HISTORY
#
# Date Ticket# Engineer Description
# ------------ ---------- ----------- -----------------------------
# 11/28/2016 5749 randerso Changed calls to addTextList
# to use default parameters
#
##
##
# This is a base file that is not intended to be overridden.
##
import PhraseBuilder
import types
import TimeRange
import logging
class WxPhrases(PhraseBuilder.PhraseBuilder):
def __init__(self):
PhraseBuilder.PhraseBuilder.__init__(self)
self.log = logging.getLogger("FormatterRunner.WxPhrases.WxPhrases")
############################################
### WEATHER PHRASES
def standard_weather_phraseMethods(self):
return [
self.preProcessWx,
self.separateNonPrecip,
self.consolidateVisibility,
self.checkLocalEffects,
self.combineWords,
self.fillNulls,
self.timeDescriptorModeration,
self.checkResolution,
self.assembleSubPhrases,
self.postProcessPhrase,
]
### WX
def pop_wx_lower_threshold(self, tree, node):
# Pop-related Wx will not be reported if Pop is below this threshold
return self.popThreshold(tree, node, self._hoursSChcEnds, 15.0, 25.0)
def pop_related_flag(self, tree, node, subkey):
# These are weather subkeys that are related to PoP and
# should be not be reported if pop is low
wxType = subkey.wxType()
if wxType in ["ZR","R","RW","S","SW", "T", "IP"]:
if wxType == "SW" or wxType == "RW":
if subkey.intensity() == "--":
return 0
if wxType == "T" and "Dry" in subkey.attributes():
return 0
return 1
else:
return 0
def precip_related_flag(self, tree, node, subkey):
# These are weather types that are precip versus non-precip
# and could be separated into different phrases from the
# non-precip weather types.
if subkey.wxType() in ["ZR", "R", "RW", "S", "SW", "T", "ZL", "L", "IP"]:
return 1
else:
return 0
def filter_subkeys_flag(self):
# Filtering and condensing of weather subkeys.
# If you do not want subkeys to be condensed and filtered, override this
# variable and set it to 0.
return 1
def wxHierarchies(self):
# This is the hierarchy of which coverage and intensity to choose if
# wxTypes are the same and to be combined into one subkey.
return {
"wxType":["WP", "R", "RW", "T", "L", "ZR", "ZL", "S", "SW",
"IP", "F", "ZF", "IF", "IC", "H", "BS", "BN", "K", "BD",
"FR", "ZY", "BA", "<NoWx>","<Invalid>"],
"coverage":["Def","Wide","Brf","Frq", "Ocnl","Pds", "Inter",
"Lkly","Num","Sct","Chc","Areas",
"SChc","WSct","Iso","Patchy","<NoCov>","<Invalid>"],
"intensity":["+","m","-","--","<NoInten>","<Invalid>"],
"visibility":["0SM", "1/4SM", "1/2SM", "3/4SM", "1SM", "11/2SM", "2SM",
"21/2SM", "3SM", "4SM", "5SM", "6SM", "P6SM", "<NoVis>","<Invalid>"],
}
def similarCoverageLists(self, tree, node, subkey1, subkey2):
# Lists of coverages that should be combined or considered equal.
# Each list should be ordered from weaker to stronger
# and the stronger coverage will be kept when subkeys are
# combined.
#
# These lists are examined when combining sub-phrases and when
# determining if there is a local effect to report.
# Called by PhraseBuilder:checkWeatherSimilarity
#
return [
['SChc', 'Iso'],
['Chc', 'Sct'],
['Lkly', 'Num'],
['Brf', 'Frq', 'Ocnl', 'Pds', 'Inter', 'Def', 'Wide'],
]
def wxCombinations(self):
# This is the list of which wxTypes should be combined into one
# WITHIN a sub-phrase.
# For example, if ("RW", "R") appears, then wxTypes of "RW" and "R" will
# be combined into one key and the key with the dominant coverage will
# be used as the combined key.
# You may also specify a method which will be
# -- given arguments subkey1 and subkey2 and
# -- should return
# -- a flag = 1 if they are to be combined, 0 otherwise
# -- the combined key to be used
# Note: The method will be called twice, once with (subkey1, subkey2)
# and once with (subkey2, subkey1) so you can assume one ordering.
# See the example below, "combine_T_RW"
#
return [
("RW", "R"),
("SW", "S"),
self.combine_T_RW,
]
def combine_T_RW(self, subkey1, subkey2):
# Combine T and RW only if the coverage of T
# is dominant over OR equal to the coverage of RW and
# RW does not have + intensity
wxType1 = subkey1.wxType()
wxType2 = subkey2.wxType()
if wxType1 == "T" and wxType2 == "RW":
if subkey2.intensity() != "+":
order = self.dominantCoverageOrder(subkey1, subkey2)
if order == -1 or order == 0:
return 1, subkey1
return 0, None
# Customizing Weather Phrases
def wxCoverageDescriptors(self):
# This is the list of coverages, wxTypes, intensities, attributes for which special
# weather coverage wording is desired. Wildcards (*) can be used to match any value.
# If a weather subkey is not found in this list, default wording
# will be used from the Weather Definition in the server.
# The format of each tuple is:
# (coverage, wxType, intensity, attribute, descriptor)
# For example:
#return [
# ("Chc", "*", "*", "*", "a chance of"),
# ]
# NOTE: descriptor can be a method taking (tree, node, subkey) as arguments
return []
def wxTypeDescriptors(self):
# This is the list of coverages, wxTypes, intensities, attributes for which special
# weather type wording is desired. Wildcards (*) can be used to match any value.
# If a weather subkey is not found in this list, default wording
# will be used from the Weather Definition in the server.
# The format of each tuple is:
# (coverage, wxType, intensity, attribute, descriptor)
# NOTE: descriptor can be a method taking (tree, node, subkey) as arguments
return [
("*", "SW", "--", "*", "flurries"),
("*", "RW", "*", "*", self.rainShowersDescriptor),
("*", "T", "*", "Dry", "dry thunderstorms"),
]
def rainShowersDescriptor(self, tree, node, subkey):
if subkey.intensity() == "--":
return "sprinkles"
if tree is None:
return "showers"
t = tree.stats.get(
"T", node.getTimeRange(),
node.getAreaLabel(), statLabel="minMax",
mergeMethod="Min")
if t is None:
return "showers"
if t < 60:
return "rain showers"
else:
return "showers"
def wxIntensityDescriptors(self):
# This is the list of coverages, wxTypes, intensities, attribute for which special
# weather intensity wording is desired. Wildcards (*) can be used to match any value.
# If a weather subkey is not found in this list, default wording
# will be used from the Weather Definition in the server.
# The format of each tuple is:
# (coverage, wxType, intensity, attribute, descriptor)
# NOTE: descriptor can be a method taking (tree, node, subkey) as arguments
return [
("*", "RW", "--", "*", ""),
("*", "RW", "-", "*", ""),
("*", "R", "--", "*", "light"),
("*", "R", "-", "*", ""),
("*", "R", "+", "*", ""),
("*", "RW", "+", "*", ""),
("*", "SW", "--", "*", ""),
("*", "SW", "-", "*", ""),
("*", "SW", "+", "*", ""),
("*", "S", "--", "*", "very light"),
("*", "S", "-", "*", ""),
("*", "S", "+", "*", ""),
("*", "T", "+", "*", ""),
("*", "ZR", "--", "*", "light"),
("*", "ZR", "+", "*", ""),
("*", "L", "*", "*", ""),
("*", "F", "+", "*", "dense"),
("*", "IP", "+", "*", ""),
]
def wxAttributeDescriptors(self):
# This is the list of coverages, wxTypes, intensities, attributes, for which special
# weather attribute wording is desired. Wildcards (*) can be used to match any value.
# If a weather subkey is not found in this list, default wording
# will be used from the Weather Definition in the server.
# The format of each tuple is:
# (coverage, wxType, intensity, attribute, descriptor)
# NOTE: descriptor can be a method taking (tree, node, subkey) as arguments
return [
("*", "T", "*", "HvyRn", ""),
("*", "T", "*", "Dry", ""),
("*", "T", "*", "GW", ""),
("*", "T", "*", "DmgW", ""),
("*", "T", "*", "FL", ""),
("*", "T", "*", "LgA", ""),
("*", "T", "*", "SmA", ""),
("*", "T", "*", "TOR", ""),
]
def weather_phrase(self):
return {
"setUpMethod": self.weather_setUp,
"wordMethod": self.weather_words,
"phraseMethods": self.standard_weather_phraseMethods()
}
def weather_setUp(self, tree, node):
resolution = node.get("resolution")
if resolution is not None:
mergeMethod = "Average"
else:
mergeMethod = "List"
elementInfoList = [self.ElementInfo("Wx", mergeMethod, self.WEATHER())]
self.subPhraseSetUp(tree, node, elementInfoList, self.wxConnector,
resolution)
node.set("allTimeDescriptors", 1)
if self.areal_sky_flag(tree, node):
self.disableSkyRelatedWx(tree, node)
return self.DONE()
def preProcessWx(self, tree, phrase):
#print '---------------------------------------------------------------'
#print 'in _preProcessWxStats -> ', phrase.get("name"), phrase.getTimeRange()
#time1 = time.time()
# Create a new list to hold all our subkeys and timeRanges
newStats = []
# Create a variable to hold the subphrase methodList
methodList = None
# Look through each subphrase of this phrase
resolution = phrase.get("resolution")
for subPhrase in phrase.childList:
# If we do not have a copy of the subPhrase methodList - make one
if methodList is None:
methodList = subPhrase.get("methodList")
# Get stats for this subphrase
if resolution is not None:
tr = subPhrase.getTimeRange()
rankList = tree.stats.get(
'Wx', tr, subPhrase.getAreaLabel(), mergeMethod="Average")
statList = [(rankList, tr)]
else:
statList = tree.stats.get(
'Wx', subPhrase.getTimeRange(),subPhrase.getAreaLabel())
if statList is None:
return self.DONE()
# Gather the subkeys and ranks by time range
for (rankList, timeRange) in statList:
if rankList is None:
continue
for subkey, rank in rankList:
newStats.append((subkey, rank, timeRange, 0))
# Remove this node from the tree
#subPhrase.remove()
# Make a new list to hold combined stats
combinedStats = []
# Define a variable to track number of changes made to new Wx stats
changes = 1 # We want at least one pass through the stats
# Keep consolidating new statistics until no changes are made
while len(newStats) > 0 and changes > 0:
# No changes made yet this pass
changes = 0
# If we have already combined Wx statistics
if len(combinedStats) > 0:
# Update the list of Wx stats we need to consolidate
newStats = combinedStats
# Combine as many Wx stats as possible
(changes, combinedStats) = self.combineWxStats(
tree, subPhrase, newStats)
# Make a new dictionary to filter consolidated stats by time
finalStatDict = {}
finalKeys = []
# For each combined Wx statistic
for (curKey, curRank, curTR, curCombined_flag) in combinedStats:
# If the dictionary does not have this time range currently
if curTR not in finalStatDict:
# Add this tuple to the dictionary
finalStatDict[curTR] = [(curKey, curRank)]
finalKeys.append(curTR)
# Otherwise
else:
tempList = finalStatDict[curTR]
tempList.append((curKey, curRank))
finalStatDict[curTR] = tempList
#print "\nPhrase :", phrase.get("name")
#print 'finalStatDict = ', finalStatDict
#print "timeRanges", finalKeys
## for su100bPhrase in phrase.childList:
## print '------------------------------------------------------------'
## print subPhrase.printNode(subPhrase)
# Make a list to hold all the new subphrases
newSubPhraseList = []
# Sort consolidated time ranges
#print "\nfinalKeys before sort", finalKeys
finalKeys.sort(key=self.orderTimeRanges)
#print "\nfinalKeys after sort", finalKeys
# Create new nodes for each of our consolidated subphrases
for timeRange in finalKeys:
# Create a new subphrase for the consolidated Wx types and times
newSubPhrase = tree.makeNode([], methodList, phrase)
statDict = {}
statDict['Wx'] = finalStatDict[timeRange]
newSubPhrase.set("elementName", 'Wx')
newSubPhrase.set("changeFlag", 0)
newSubPhrase.set("parent", phrase)
newSubPhrase.set("statDict", statDict)
newSubPhrase.set("timeRange", timeRange)
newSubPhrase.set("childList", [])
newSubPhrase.set("timeDescFlag", 1) # always include timers
# Keep track of this new node
newSubPhraseList.append(newSubPhrase)
# Replace the old subphrases with the new subphrases
phrase.set("childList", newSubPhraseList)
# Indicate Wx stats have been preprocessed\
#print " Time: ", time.time() - time1
return self.DONE()
# Define a method to combine Wx stats per my relaxed rules
def combineWxStats(self, tree, subPhrase, statList):
# Define some variables to keep track of combined Wx subkeys
combinedStats = []
changes = 0
combinedTypes = []
# Look through all our subkeys
for index in range(len(statList)-1):
# Get the info about this key
(curKey, curRank, curTR, curCombined_flag) = statList[index]
# Break this subkey into its components
curCov = curKey.coverage()
curType = curKey.wxType()
curInten = curKey.intensity()
curVis = curKey.visibility()
curAttr = curKey.attributes()
# Shorten the list of keys we need to test
searchStats = statList[index+1:]
## print 'searchStats = ', searchStats
# See if we can combine this key with any of the other keys
for testIndex in range(len(searchStats)):
# Get the infor about this key
(testKey, testRank, testTR, testCombined_flag) = searchStats[testIndex]
# We will only try combining if neighboring time ranges
if curTR.endTime() != testTR.startTime():
continue
# See if there is a significant difference between these
# Wx subkeys
# match is zero if there is a difference between the keys.
# is 1 or 2 if they can be combined.
match = self.checkWeatherSimilarity(
tree, subPhrase, [(curKey, curRank)],[(testKey, testRank)],
tr1=curTR, tr2=testTR)
#print 'combineWxStats %s <=> %s -> %d' % (curKey, testKey, match)
# If these keys could be combined, and this type has not been
# combined yet
if match > 0 and curType not in combinedTypes:
newSubkey = self.makeAggregateSubkey(curKey, curRank, testKey, testRank)
#print "newSubkey", newSubkey
# Mark both keys in current lists as being combined
statList[index] = (curKey, curRank, curTR, curCombined_flag + 1)
searchStats[testIndex] = (testKey, testTR,
testCombined_flag + 1)
# Make sure the search key is also marked as combined
# in the main stats list
statList[index+testIndex+1] = (testKey, testRank, testTR,
testCombined_flag + 1)
# Make a new time range for this combined key
newTR = TimeRange.TimeRange(curTR.startTime(), testTR.endTime())
# Take highest rank of the two subkeys to be combined
if curRank > testRank:
newRank = curRank
else:
newRank = testRank
# Add this new subkey to the consolidated stats
combinedStats.append((newSubkey, newRank, newTR, 0))
# Do not try to combine this Wx subkey any more
changes = changes + 1
combinedTypes.append(curType)
# Make sure we did not miss any subkeys that were not combined
for (curKey, curRank, curTR, curCombined_flag) in statList:
# If this key was not previously combined
if curCombined_flag == 0:
# Add this key to the consolidated stats
combinedStats.append((curKey, curRank, curTR, curCombined_flag))
## print '\n', changes, combinedTypes
## print 'combinedStats = '
## print combinedStats
## print '**********************************************************'
# Return number of changes and current combined Wx stats
return (changes, combinedStats)
def useSimple(self, tree, node, rankList, subkeys):
# Return 1 if we want to use simple weather phrasing
# where the weather subkeys are simply connected by
# a conjunction i.e. do not use "with", "mixedWith", "possiblyMixedWith",
# "withPocketsOf". For example:
#
# Simple wording:
# Chance of rain and snow and slight chance of sleet in the evening.
# Mostly cloudy with chance of rain and a slight chance of thunderstorms.
#
# Complex wording:
# Chance of rain and snow possibly mixed with sleet in the evening.
# Chance of rain with possible thunderstorms in the evening.
#
numSubkeys = len(subkeys)
if numSubkeys <= 2:
return 1
elif numSubkeys <= 4:
# Check for coverage groupings
# If there are only 1 or 2 coverage groupings, use simple wording
covList = []
for subkey in subkeys:
cov = subkey.coverage()
if cov not in covList:
covList.append(cov)
if len(covList) <= 2:
return 1
else:
return 0
else:
return 0
def rankWordingFuzzFactor(self, tree, node, subkey1, subkey2):
# Used in weather wording to determine if
# subkeys have significantly different ranks.
# If so, then wording such as "possibly mixed with" or
# "with pockets of" could be used.
return 10
def wxConjunction(self, tree, node, subkey1, rank1, subkey2, rank2):
if subkey1 is not None:
attr1 = subkey1.attributes()
else:
attr1 = []
attr2 = subkey2.attributes()
if "OR" in attr1 or "OR" in attr2:
return " or "
# If Def is followed by Lkly, return "with" to avoid
# ambiguity
cov1 = subkey1.coverage()
cov2 = subkey2.coverage()
if cov1 == "Def" and cov2 == "Lkly":
return " with "
else:
return " and "
def withPossible(self, tree, node, subkey1, rank1, subkey2, rank2):
# Wording to use if subkey1 has higher rank or is dominant over subkey2
# Handle "with little or no rain"
wxType1 = subkey1.wxType()
wxType2 = subkey2.wxType()
if wxType1 in ["T"] and wxType2 in ["RW", "R"]:
self.includeValue = 0 # i.e. do not add rain or rain showers wording
return " with little or no rain"
return " with possible "
def withPhrase(self, tree, node, subkey1, rank1, subkey2, rank2):
# Wording to use if subkey1 has similar rank and coverage to subkey2
wxType1 = subkey1.wxType()
wxType2 = subkey2.wxType()
#print "wxType1, 2", wxType1, wxType2
if wxType2 in ["T"]:
return " and "
else:
return " with "
def withPocketsOf(self, tree, node, subkey1, rank1, subkey2, rank2):
# Wording to use if subkey2 has higher rank or is dominant over subkey1
return " with pockets of "
def possiblyMixedWith(self, tree, node, subkey1, rank1, subkey2, rank2):
# Wording to use if subkey1 has higher rank or is dominant over subkey2
wxType1 = subkey1.wxType()
wxType2 = subkey2.wxType()
# Handle "with possible thunderstorms"
if wxType2 in ["T"]:
return " with possible "
# Handle "with little or no rain"
if wxType1 in ["T"] and wxType2 in ["RW", "R"]:
self.includeValue = 0 # i.e. do not add rain or rain showers wording
return " with little or no rain"
return " possibly mixed with "
def mixedWith(self, tree, node, subkey1, rank1, subkey2, rank2):
# Wording to use if subkey1 has similar rank and coverage to subkey2
wxType1 = subkey1.wxType()
wxType2 = subkey2.wxType()
#print "wxType1, 2", wxType1, wxType2
if wxType2 in ["T"]:
return " and "
else:
return " mixed with "
def weather_words(self, tree, node):
# Create a phrase to describe a list of weather sub keys for one sub-period
# Get rankList
statDict = node.getStatDict()
rankList = self.getStats(statDict, "Wx")
if self._debug:
print("\n SubKeys in weather_words", rankList)
print(" TimeRange", node.getTimeRange(), node.getAreaLabel())
print(" Phrase name", node.getAncestor("name"))
if rankList is None or len(rankList) == 0:
return self.setWords(node, "")
# Check against PoP
rankList = self.checkPoP(tree, node, rankList)
# Check visibility
subkeys = self.getSubkeys(rankList)
if self.checkVisibility(tree, node, subkeys):
return self.setWords(node, "null")
# Get the weather words
words = self.getWeatherWords(tree, node, rankList)
node.set('reportedRankList', rankList)
# Add embedded visibility
words = self.addEmbeddedVisibility(tree, node, subkeys, words)
if words == "":
words = "null"
if self._debug:
print(" Setting words", words)
# To replace multiple "and's" with ellipses
words = self.useCommas(tree, node, words)
return self.setWords(node, words)
def checkPoP(self, tree, node, rankList):
# Do not report pop_related subkeys if PoP is below
# pop_wx_lower_threshold
popThreshold = self.pop_wx_lower_threshold(tree, node)
lowPopFlag = self.lowPop_flag(tree, node, popThreshold)
# Force a check of all weather subkeys
newList = []
for subkey, rank in rankList:
# If PoP threshold >= 25% and coverage is 'SChc' or 'Iso'
# don't mention the subkey
if popThreshold >= 25 and subkey.coverage() in ['SChc', 'Iso']:
continue
# If we have a low PoP, and this is not a precip-related subkey
# report it e.g. Fog
if lowPopFlag == 1:
if not self.pop_related_flag(tree, node, subkey):
newList.append((subkey, rank))
# If there is no low PoP, report the subkey
else:
newList.append((subkey, rank))
return newList
def getWeatherWords(self, tree, node, rankList):
# For each WeatherSubKey, add it to the phrase
# Use ranking of subkeys to form wording:
# If useSimple produce simple wording e.g.
# Chance of rain and snow and slight chance of sleet and freezing rain.
# Otherwise:
# Create a phrase of the form:
# <list1 of subkeys> <conjunction> <list2 of subkeys>
# where:
# list1 and list2 are lists of subkeys separated by
# '...' or 'and' e.g. Snow...rain and sleet
# list1 subkeys have similar coverages and ranks
# list2 subkeys have coverages or ranks significantly
# different from those in list1
# conjunction connects the 2 lists appropriately, e.g.
# Snow and rain with possible sleet and freezing rain.
# Rain and drizzle with pockets of snow.
rankList.sort(key=self.rankedSortOrder)
length = len(rankList)
words = ""
index = 0
# For non-simple phrasing, have we switched to the second list
# using the conjunction yet?
switchConjunction = 0
# Begin by including coverage with weather value
includeCovInten = 1
# Handle "Likely" specially
addLkly = 0
subkeys = self.getSubkeys(rankList)
useSimple = self.useSimple(tree, node, rankList, subkeys)
prevCoverage = prevSubkey = prevRank = None
prevConj = ""
for index in range(len(rankList)):
subkey, rank = rankList[index]
# If not last one, determine nextCoverage
if index < length-1:
nextSubkey, nextRank = rankList[index+1]
else:
nextSubkey = None
# Set so that value is included UNLESS re-set by one of the
# sub-methods e.g. mixedWith, possiblyMixedWith, etc..
self.includeValue = 1
# Add conjunction for non-simple words
if not useSimple:
words, conj, switchConjunction, includeCovInten, addLkly = \
self.addWxConjunction(
tree, node, words, prevSubkey, prevRank, subkey, rank,
index, switchConjunction, includeCovInten, addLkly)
# Get string for subkey checking previous and next coverage
value, prevCoverage = self.weather_value(
tree, node, subkey, prevCoverage, nextSubkey,
includeCovInten=includeCovInten)
if self.includeValue == 1:
if value == "":
# If empty value string, remove the previous conjunction
# so we don't end up with something like "rain and"
words = self.removeLast(words, prevConj)
else:
words = words + value
# if last one, do not add conjunction
if index == length - 1:
break
if useSimple:
conj = self.wxConjunction(tree, node, subkey, rank, nextSubkey, nextRank)
words = words + conj
prevSubkey = subkey
prevConj = conj
prevRank = rank
if addLkly:
words = words + " likely"
return words
def addWxConjunction(self, tree, node, words, prevSubkey, prevRank, subkey, rank,
index, switchConjunction, includeCovInten, addLkly):
# Check to see if we can switch to "with" or "with pockets of"
# OR just add the regular conjunction
# NOTE:"mixed" weather will be implemented when the samplers can support it.
conj = ""
#print "\nin addWxConj", prevSubkey, prevRank, subkey, rank
if not switchConjunction and index > 0:
includeCovInten = 0
similarCovs = self.similarCoverages(tree, node, prevSubkey, subkey)
rankWordingFuzzFactor = self.rankWordingFuzzFactor(
tree, node, prevSubkey, subkey)
# If the current rank is significantly less than the previous one
# or if the coverage is significantly different from the previous one
#print "prevSubkey, subkey", prevSubkey, subkey
#print "similarCovs", similarCovs
if rank <= prevRank - rankWordingFuzzFactor or similarCovs == 0:
if similarCovs == 1 or similarCovs == 0:
# Prev subkey is dominant either by coverage or rank
method = self.withPossible
else: # similarCovs == 2 i.e. Current subkey is dominant
method = self.withPocketsOf
switchConjunction = 1
conj = method(tree, node, prevSubkey, prevRank, subkey, rank)
if conj == "":
if index > 0:
if prevSubkey.coverage() == "Lkly":
addLkly = 1
conj = self.wxConjunction(tree, node, prevSubkey, prevRank,
subkey, rank)
words = words + conj
#print "returning", words+conj
return words, conj, switchConjunction, includeCovInten, addLkly
def weather_value(self, tree, node, subkey, prevCoverage=None,
nextSubkey=None, typeOnly=0,
includeCovInten=1):
"Return a phrase for the WeatherSubkey"
# If the prevCoverage is the same, then do not repeat it.
# e.g. "Widespread rain and snow" instead of
# "Widespread rain and widespread snow"
# "Likely" is a special case because it follows the nouns,
# so we need to look at the nextCoverage to get:
# e.g. "Rain and snow likely" instead of
# "Rain likely and snow likely"
# If typeOnly is set to one, only the Type phrase is returned.
wxDef = subkey.wxDef()
wxType = subkey.wxType()
if wxType == '<NoWx>':
wxType = ""
else:
wxType = wxDef.typeDesc(wxType).lower()
inten = subkey.intensity()
if inten == '<NoInten>':
inten = ""
else:
inten = wxDef.intensityDesc(subkey.wxType(), inten).lower()
if inten.find("moderate") != -1:
inten = ""
attrList = subkey.attributes()
attrList = self.removeDups(attrList)
attrTextList = []
for attr in attrList:
# Ignore non-text attributes
if attr == "MX" or attr == "OR" or attr == "Primary" or attr == "Mention":
continue
attrDesc = wxDef.attributeDesc(subkey.wxType(), attr).lower()
# Use the wxAttributeDescriptors if provided
attrDescs = self.call(self.wxAttributeDescriptors, tree, node)
for des_cov, des_type, des_inten, des_attr, desc in attrDescs:
if self.matchSubkey(subkey, des_cov, des_type, des_inten, des_attr, [attr]) == 1:
attrDesc = self.getWxDesc(tree, node, subkey, desc)
if attrDesc != "":
attrTextList.append(attrDesc)
# Determine coverage. Check for repetition.
covDescs = self.call(self.wxCoverageDescriptors, tree, node)
cov = self.getCoverage(tree, node, subkey, covDescs, wxDef, attrList)
# Make a copy of this coverage for later use
copyCov = cov
#print "cov, prev", cov, prevCoverage
if cov == prevCoverage and not prevCoverage == "likely":
cov = ""
elif cov == "likely":
nextCoverage = self.getCoverage(
tree, node, nextSubkey, covDescs, wxDef, attrList)
if cov == nextCoverage:
cov = ""
#print "result", cov
# Use wxTypeDescriptors and wxIntensityDescriptors if provided
typeDescs = self.call(self.wxTypeDescriptors, tree, node)
intenDescs = self.call(self.wxIntensityDescriptors, tree, node)
for des_cov, des_type, des_inten, des_attr, desc in typeDescs:
if self.matchSubkey(subkey, des_cov, des_type, des_inten,des_attr, attrList) == 1:
wxType = self.getWxDesc(tree, node, subkey, desc)
for des_cov, des_type, des_inten, des_attr, desc in intenDescs:
if self.matchSubkey(subkey, des_cov, des_type, des_inten,des_attr, attrList) == 1:
inten = self.getWxDesc(tree, node, subkey, desc)
# Handle special cases and clean up
if cov == "definite":
cov = ""
# Hail -- "Large Hail" and "Small Hail" attributes
# get converted to adjectives instead of attributes.
if wxType == "hail":
hailAttr = None
if "large hail" in attrTextList:
hailAttr = "large hail"
adj = "large"
if "small hail" in attrTextList:
hailAttr = "small hail"
adj = "small"
if hailAttr is not None:
wxType = adj + " hail"
newAttrs = []
for attr in attrTextList:
if attr != hailAttr:
newAttrs.append(attr)
attrTextList = newAttrs
# Arrange the order of the words
if typeOnly == 1:
return wxType, cov
if includeCovInten == 0:
cov = ""
inten = ""
word1 = cov
word2 = inten
word3 = wxType
# Handle special case of "likely"
if cov == "likely":
word1 = inten
word2 = wxType
word3 = cov
# Put coverage, intensity and wxType together
if word2 == "":
phrase = word1 + " " + word3
else:
phrase = word1 + " " + word2 + " " + word3
phrase = phrase.strip()
# Add attributes
phrase = self.addTextList(phrase, attrTextList)
phrase = phrase.replace("with in", "in")
if cov == "":
cov = copyCov
return phrase, cov
def getIndex(self, hierarchy, value):
list = self.wxHierarchies()[hierarchy]
return list.index(value)
def getSubkeys(self, rankList):
subkeys = []
if rankList is None:
return subkeys
for subkey, rank in rankList:
if subkey.wxType() == "<NoWx>":
continue
subkeys.append(subkey)
return subkeys
def checkVisibility(self, tree, node, subkeys):
## If no visibility threshold is set, produce weather words as normal.
## If a visibility threshold is set:
## --If there are significant weather keys in the weather grid,
## produce the weather words regardless of the visibility.
## --If there is no visibility specified in the grids,
## produce the weather words IF there are significant
## weather keys in the grids.
## --If there is a visibility specified in the grids, check to see
## if it is less than the visibility threshold.
## If it is, produce the weather words.
## --If there is a visibility specified in the grids and it is
## greater or equal to threshold and there are no significant weather
## keys, produce "null" weather words.
visThreshold = self.visibility_wx_threshold(tree, node)
if visThreshold is not None:
produceWords = 0
# Check for significant keys
significantKeys = self.significant_wx_visibility_subkeys(tree, node)
sigFlag = self.findSubkeys(subkeys, significantKeys)
# If significant weather keys, produce words regardless of vis
if sigFlag:
produceWords = 1
else: # sigFlag is 0
lowVisNM = self.getVis(subkeys)
# If lowVisNM is None, we will not produce words
if lowVisNM is not None:
# Produce words only if lowVisNM < visThreshold
if lowVisNM < visThreshold:
produceWords = 1
if not produceWords:
return 1
return 0
def addEmbeddedVisibility(self, tree, node, subkeys, words):
# Add embedded visibility wording
if self.embedded_visibility_flag(tree, node):
# Check for visibility having consolidated to separate phrase
visFlag = node.parent.get("reportVisibility")
if visFlag != 0:
# Find low visibility for subkeys
lowVisNM = self.getVis(subkeys)
if lowVisNM is not None:
lowVisNM = self.roundStatistic(tree, node, lowVisNM, "Visibility")
# If below null_nlValue, report it
visThreshold = self.nlValue(self.null_nlValue(
tree, node, "Visibility", "Visibility"), lowVisNM)
if lowVisNM < visThreshold:
visWords = self.nlValue(
self.visibility_weather_phrase_nlValue(tree, node), lowVisNM)
significantKeys = self.significant_wx_visibility_subkeys(tree, node)
sigFlag = self.findSubkeys(subkeys, significantKeys)
# If the weather words are not null and this is a Wx
# obstruction for which we should report vsby
if words != "": # and sigFlag != 0: DR_18894 change
visWords = " with " + visWords
words = words + visWords
return words
def separateNonPrecip_threshold(self, tree, node):
# Number of sub-phrases required to separate precip from
# non-precip
return 1
def separateNonPrecip(self, tree, node):
# See if ready to process
if not self.phrase_trigger(tree, node, setUpOnly=1):
return
# If > designated subPhrases, separate into precip/non-precip
statList = self.getSubStats(node, "Wx")
length = len(statList)
if self.__dict__.get('_leDebug', 0):
print("\n\nseparateNonPrecip disabled", node, length, \
node.getAncestor("disabledSubkeys"))
print(" timerange", node.getTimeRange())
print(" statList", statList)
#print " doneList", node.doneList
if length >= self.separateNonPrecip_threshold(tree, node):
precip = []
nonPrecip = []
for rankList in statList:
subkeys = self.getSubkeys(rankList)
for subkey in subkeys:
if subkey.wxType() == "<NoWx>":
continue
if self.precip_related_flag(tree, node, subkey):
precip.append(subkey)
else:
nonPrecip.append(subkey)
if self.__dict__.get('_leDebug', 0):
print("precip, nonPrecip", precip, nonPrecip)
if len(precip) >= 1 and len(nonPrecip) >= 1:
self.splitWxPhrase(tree, node, precip, nonPrecip, [self.separateNonPrecip])
return self.DONE()
def consolidateVisibility(self, tree, node):
# If visibility is constant throughout subphrases and non-null,
# separate out into its own phrase
# See if ready to process
if not self.phrase_trigger(tree, node, setUpOnly=1):
return
subPhrases = node.get("childList")
if len(subPhrases) <= 1:
return self.DONE()
lowVis = None
firstTime = 1
#print "\nconsolidating"
for subPhrase in node.childList:
statDict = subPhrase.getStatDict()
rankList = self.getStats(statDict, "Wx")
subkeys = self.getSubkeys(rankList)
subLowVis = self.getVis(subkeys)
#print "low vis", subLowVis, subPhrase.getTimeRange()
if firstTime:
lowVis = subLowVis
firstTime = 0
elif subLowVis != lowVis:
# Visibility for this subPhrase differs from previous
# so we can't consolidate
return self.DONE()
if lowVis is None:
return self.DONE()
# Check to see if lowVis is non-null
visThreshold = self.nlValue(self.null_nlValue(
tree, node, "Visibility", "Visibility"), lowVis)
if lowVis <= visThreshold:
# Need to report as separate phrase
newPhrase = tree.addPhraseDef(node, self.visibility_phrase)
# Turn off visibility reporting for this weather phrase
node.set("reportVisibility", 0)
return self.DONE()
def consolidateWx(self, tree, node):
# If any wxTypes span all subPhrases, separate into their own phrase
statList = self.getSubStats(node, "Wx")
length = len(statList)
subkeyDict = {}
if self.__dict__.get('leDebug', 0):
print("\n\nConsolidating disabled", node.getAncestor("disabledSubkeys"))
print(" timerange", node.getTimeRange())
print(" statList", statList)
#print " doneList", node.doneList
if length > 1:
# Count occurrences of each weather key
for subkeys in statList:
for subkey in subkeys:
if subkey not in subkeyDict:
subkeyDict[subkey] = 1
else:
subkeyDict[subkey] += 1
# Find subkeys to disable in first phrase and second phrase, respectively
list1 = []
list2 = []
for (subkey, count) in subkeyDict.items():
if count >= length:
list2.append(subkey)
else:
list1.append(subkey)
if self.__dict__.get('_leDebug', 0):
print("list1, list2", list1, list2)
if len(list1) > 0 and len(list2) > 0:
self.splitWxPhrase(
tree, node, list1, list2,
[self.consolidateWx, self.separateNonPrecip,
self.skyPopWx_consolidateWx])
return self.DONE()
def subPhrase_limit(self, tree, node):
# If the number of sub-phrases is greater than this limit, the weather
# phrase will use 6-hour instead of the higher resolution to produce:
#
# Occasional snow possibly mixed with sleet and freezing
# drizzle in the morning, then a chance of rain possibly mixed wiht snow
# and sleet and freezing drizzle in the afternoon.
#
# instead of:
# Occasional snow in the morning. Chance of light sleet and
# slight chance of light freezing drizzle in the late morning and
# early afternoon. Chance of snow early in the afternoon. Chance of
# rain in the afternoon.
return 3
def checkResolution(self, tree, node):
# Check to see if there are too many sub-phrases and we need to re-do the
# phrase in lower resolution. The limit is determined by "subPhrase_limit".
# This currently assumes we have a 3 or greater resolution and want to go to
# a 6-hour resolution.
# See if ready to process
if not self.phrase_trigger(tree, node):
return
# Count the number of non-empty phrases
#print "\n In check resolution", node
count = 0
for subPhrase in node.get("childList"):
words = subPhrase.get("words")
if words == "":
continue
#print "words", subPhrase, words
count += 1
if count > self.subPhrase_limit(tree, node):
#print "count", count
# Create a new node in it's place with a new
# resolution set
newPhraseDef = getattr(self, node.getAncestor('name'))
newPhrase = tree.addPhraseDef(node, newPhraseDef)
newPhrase.set("disabledSubkeys", node.get("disabledSubkeys"))
curResolution = node.get("resolution")
if curResolution is not None:
# If we have already re-set the resolution and we are still over the
# sub-phrase limit, we'll have to decrease the resolution some more
# to try and reduce the number of sub-phrases.
# This is necessary because of the way preProcessWx works:
# For example, even if we have only 2 time periods sampled,
# they can result in 3 or more sub-phrases depending on the
# complexity of weather.
# Example: Hours 1-6 Chc RW Chc L
# Hours 7-12 Chc SW Chc L
# Results in 3 sub-phrases
# Hours 1-12 Chc L
# Hours 1-6 Chc RW
# Hours 7-12 Chc SW
newResolution = curResolution * 2
else:
newResolution = 6
newPhrase.set("resolution", newResolution)
for key in ["spawnedWxPhrases", "conjunctiveQualifier",
"embeddedQualifier", "localEffect", "localEffectsList",
"firstElement", "elementName"]:
newPhrase.set(key, node.get(key))
#print "making newPhrase", newPhrase
#print "parent should be", node.parent
#tree.printNode(newPhrase)
# Remove this node
node.remove()
return self.DONE()
def severeWeather_phrase(self):
return {
"setUpMethod": self.severeWeather_setUp,
"wordMethod": self.severeWeather_words,
"phraseMethods": [
self.preProcessWx,
self.combineWords,
self.fillNulls,
self.timeDescriptorModeration,
self.assembleSubPhrases,
self.postProcessPhrase,
],
}
def severeWeather_setUp(self, tree, node):
elementInfoList = [self.ElementInfo("Wx", "List", self.WEATHER())]
self.subPhraseSetUp(tree, node, elementInfoList, self.wxConnector)
# Set this flag used by the "checkWeatherSimilarity" method
node.set("noIntensityCombining", 1)
self.determineSevereTimeDescriptors(tree, node)
return self.DONE()
def determineSevereTimeDescriptors(self, tree, node):
wxStats = tree.stats.get("Wx", node.getTimeRange(), node.getAreaLabel())
thunderThru = 1 # T throughout the time period
severeThru = 1 # T+ throughout the time period
allThunderSevere = 1 # All T that appears in the period is +
if wxStats is not None:
for subkeys, tr in wxStats:
thunderFound = 0
severeFound = 0
for subkey, rank in subkeys:
#print " subkey", subkey
if subkey.wxType() == "T":
thunderFound = 1
if subkey.intensity() == "+":
severeFound = 1
else:
allThunderSevere = 0
if not severeFound:
severeThru = 0
if not thunderFound:
thunderThru = 0
#print "thunderThru, severeThru, allThunderSevere", thunderThru, severeThru, allThunderSevere
if thunderThru == 1:
if severeThru == 1:
noTD = 1
else:
noTD = 0
elif allThunderSevere == 1:
noTD = 1
else:
noTD = 0
#print "noTD", noTD
if noTD:
node.set("noTimeDescriptors", 1)
#print "setting", node
return
def severeWeather_words(self, tree, node):
"If T +, produce phrase. Report attributes of T."
# Wx Statistics: rankedWx
statDict = node.getStatDict()
rankList = self.getStats(statDict, "Wx")
if rankList is None or len(rankList) == 0:
return self.setWords(node, "")
# Check against PoP
rankList = self.checkPoP(tree, node, rankList)
subkeyList = self.getSubkeys(rankList)
severe = 0
thunder = 0
attrTextList = []
for subkey in subkeyList:
wxType = subkey.wxType()
if wxType == "T":
thunder = 1
intensity = subkey.intensity()
if intensity == "+":
severe = 1
wxDef = subkey.wxDef()
for attr in subkey.attributes():
if attr in ["Primary", "Mention", "Dry"]:
continue
attrText = wxDef.attributeDesc(subkey.wxType(), attr).lower()
if attrText not in attrTextList:
attrTextList.append(attrText)
if thunder == 0:
return self.setWords(node, "")
if severe == 0 and attrTextList == []:
return self.setWords(node, "")
# Add attributes to phrase
if severe == 0:
words = self.phrase_descriptor(tree, node, "thunderstorms", "Wx")
words = self.addTextList(words, attrTextList, " ") # no preposition
else:
words = self.phrase_descriptor(tree, node, "severeWeather", "Wx")
words = self.addTextList(words, attrTextList)
return self.setWords(node, words)
def heavyRainTypes(self, tree, node):
# Rain weather types that will trigger the heavyPrecip_phrase
return ["R", "RW"]
def heavySnowTypes(self, tree, node):
# Snow weather types that will trigger the heavyPrecip_phrase
return ["S", "SW"]
def heavyOtherTypes(self, tree, node):
# Weather types other than snow that will trigger the heavyPrecip_phrase
# R and RW are automatic triggers
return ["IP", "ZR", "L", "ZL"]
def heavyPrecip_phrase(self):
### NEW METHOD by Tom Spriggs/Steve Nelson/Tracy Hansen
### ZFP_Local
return {
"setUpMethod": self.heavyPrecip_setUp,
"wordMethod": self.heavyPrecip_words,
"phraseMethods": [
self.combineHeavyPrecip,
self.combineWords,
self.fillNulls,
self.timeDescriptorModeration,
self.assembleSubPhrases,
self.postProcessPhrase,
],
}
def heavyPrecip_setUp(self, tree, node):
elementInfoList = [self.ElementInfo("Wx", "List", self.WEATHER())]
self.subPhraseSetUp(tree, node, elementInfoList, self.wxConnector)
# Set this flag used by the "checkWeatherSimilarity" method
node.set("noIntensityCombining", 1)
return self.DONE()
def combineHeavyPrecip(self, tree, phrase):
# See if ready to process
if not self.phrase_trigger(tree, phrase, setUpOnly=1):
return
return self.combineChildren(tree, phrase, self.combineHeavy)
def combineHeavy(self, tree, phrase, subPhrase1, subPhrase2):
### NEW METHOD TO prevent reporting redundant phrases in complex wx
# If there is heavy precip in both subPhrase1 and subPhrase2, combine
statDict1 = subPhrase1.getStatDict()
stats1 = statDict1["Wx"]
statDict2 = subPhrase2.getStatDict()
stats2 = statDict2["Wx"]
if stats1 is None and stats2 is None:
return 1, None
if stats1 is None or stats2 is None:
return 0, None
newStats = []
heavy = [0,0]
index = 0
for wxStats in [stats1, stats2]:
for subkey, rank in wxStats:
wxType = subkey.wxType()
if subkey.intensity() == "+":
if wxType in self.heavyRainTypes(tree, phrase) or \
wxType in self.heavySnowTypes(tree, phrase) or \
wxType in self.heavyOtherTypes(tree, phrase):
heavy[index] = 1
newStats.append((subkey, rank))
index += 1
if heavy[0] and heavy[1]:
elementInfoList = phrase.get("elementInfoList")
newSubPhrase = self.combine2SubPhrases(tree, phrase, subPhrase1, subPhrase2,
elementInfoList, newStats)
return 1, newSubPhrase
else:
return 0, None
def heavyPrecip_words(self, tree, node):
### WxPhrases
self._heavyPrecipFlag = 0
self._rainfallFlag = 0
self._rainFlag = 0
self._snowFlag = 0
self._otherFlag = 0
statDict = node.getStatDict()
rankList = self.getStats(statDict, "Wx")
if rankList is None or len(rankList) == 0:
return self.setWords(node, "")
# Check against PoP
rankList = self.checkPoP(tree, node, rankList)
subkeyList = self.getSubkeys(rankList)
checkSnowTypes = self.heavySnowTypes(tree, node)
checkOtherTypes = self.heavyOtherTypes(tree, node)
words = ""
for subkey in subkeyList:
wxType = subkey.wxType()
intensity = subkey.intensity()
if intensity == "+":
for type in checkOtherTypes:
if wxType == type:
self._heavyPrecipFlag = 1
self._otherFlag = 1
for type in checkSnowTypes:
if wxType == type:
self._heavyPrecipFlag = 1
self._snowFlag = 1
if wxType == "RW":
self._heavyPrecipFlag = 1
self._rainfallFlag = 1
if wxType == "R":
self._heavyPrecipFlag = 1
self._rainFlag = 1
if self._heavyPrecipFlag == 1:
if self._otherFlag == 1:
words = self.phrase_descriptor(tree, node, "heavyPrecip", "Wx")
elif self._snowFlag == 1 and self._rainFlag == 0 and self._rainfallFlag == 0:
words = self.phrase_descriptor(tree, node, "heavySnow", "Wx")
elif self._snowFlag == 0 and self._rainFlag == 1 and self._rainfallFlag == 0:
words = self.phrase_descriptor(tree, node, "heavyRain", "Wx")
elif self._snowFlag == 0 and self._rainFlag == 0 and self._rainfallFlag == 1:
words = self.phrase_descriptor(tree, node, "heavyRainfall", "Wx")
else:
words = self.phrase_descriptor(tree, node, "heavyPrecip", "Wx")
return self.setWords(node, words)
def filterSubkeys(self, tree, node, rankList):
# Filter subkeys in rankList:
# Combine using wxCombinations
if self.filter_subkeys_flag() == 0:
return rankList
#print "rankList in filter", rankList
if rankList is None:
return rankList
if len(rankList) == 0:
return rankList
rankList, convertedFlag = self.convertToRankList(rankList)
rankList = self.combineSubKeys(tree, node, rankList)
if convertedFlag:
rankList = self.convertFromRankList(rankList)
return rankList
def convertToRankList(self, rankList):
# If the list is a simple list of subkeys,
# add a dummy rank to each entry
# and return convertedFlag = 1
if rankList == []:
return rankList, 0
entry = rankList[0]
if type(entry) is not tuple:
newList = []
for subkey in rankList:
newList.append((subkey,0))
rankList = newList
convertedFlag = 1
else:
convertedFlag = 0
return rankList, convertedFlag
def convertFromRankList(self, rankList):
# Strip the dummy ranks off the rankList
newList = []
for subkey, rank in rankList:
newList.append(subkey)
return newList
def combineSubKeys(self, tree, node, rankList):
# Compare subkeys and condense if appropriate
rankList, convertedFlag = self.convertToRankList(rankList)
done = 0
while done == 0:
combinedKey, combinedRank, index1, index2 = self.combineKeys(tree, node, rankList)
#print "combinedKey", combinedKey, index1, index2
# If no more combinations possible, we are done
if combinedKey is None:
done = 1
else:
# Make a new list:
# Set index1 to combinedKey
# Delete index2
newList = []
ind = 0
length = len(rankList)
for subkey, rank in rankList:
if ind > length-1:
break
if ind == index1:
newList.append((combinedKey, combinedRank))
elif ind != index2:
newList.append((subkey, rank))
ind = ind + 1
rankList = newList
#print "Leaving combineSubKeys", rankList
if convertedFlag:
rankList = self.convertFromRankList(rankList)
return rankList
def combineKeys(self, tree, node, rankList):
# See if any keys can be combined
# Return when the first combination is found
length = len(rankList)
if length <= 1:
return None, None, 0, 0
for index1 in range(0, length):
# March down remaining list, trying to combine
for index2 in range(index1 + 1 , length):
combinedKey, combinedRank = self.combineKey1Key2(tree, node, rankList[index1],
rankList[index2])
if combinedKey is not None:
return combinedKey, combinedRank, index1, index2
return None, None, 0, 0
def combineKey1Key2(self, tree, node, entry1, entry2):
# Combine duplicates, "near duplicates", and user-defined
# combinations from wxCombinations
subkey1, rank1 = entry1
subkey2, rank2 = entry2
wxType1 = subkey1.wxType()
wxType2 = subkey2.wxType()
cov1 = subkey1.coverage()
inten1 = subkey1.intensity()
cov2 = subkey2.coverage()
inten2 = subkey2.intensity()
combinedKey = None
combinedRank = max(rank1, rank2)
if subkey1 == subkey2:
combinedKey = subkey1
elif wxType1 == wxType2:
# In this case, we must make an aggregate
combinedKey = self.makeAggregateSubkey(subkey1, rank1, subkey2, rank2)
else:
# Try to combine using configurable wxCombinations
# May need to pick lowVis, preserve attrs here too
combinations = self.call(self.wxCombinations, tree, node)
for combination in combinations:
match, combinedKey = self.matchWxCombination(
combination, subkey1, subkey2)
if match:
break
return combinedKey, combinedRank
def matchWxCombination(self, combination, subkey1, subkey2):
# Given a combination i.e. pair (wx1, wx2) or method,
# determine if subkey1 and subkey2 should be combined.
# Return flag (match) and combinedKey
keyList = [(subkey1, subkey2), (subkey2, subkey1)]
match = 0
combinedKey = None
for key1, key2 in keyList:
if type(combination) is types.MethodType:
match, combinedKey = combination(key1, key2)
if match:
break
else:
wx1, wx2 = combination
if wx1 == key1.wxType() and wx2 == key2.wxType():
order = self.dominantCoverageOrder(key1, key2)
if order == -1 or order == 0:
combinedKey = key1
else:
combinedKey = key2
match = 1
break
return match, combinedKey
def dominantCoverageOrder(self, val1, val2):
# Order by dominant coverage -- lower indices are dominant
# If val1 coverage is dominant over val2 coverage,
# return -1, if equal return 0, else return 1
val1 = val1.coverage()
val2 = val2.coverage()
list = self.wxHierarchies()["coverage"]
try:
index1 = list.index(val1)
except:
index1 = len(list)-1
try:
index2 = list.index(val2)
except:
index2 = len(list)-1
if index1 < index2:
return -1
if index1 == index2:
return 0
if index1 > index2:
return 1
def dominantTypeOrder(self, val1, val2):
# If val1 wxType is dominant over val2 wxType
# Lower indices are dominant
# return -1, if equal return 0, else return 1
val1 = val1.wxType()
val2 = val2.wxType()
list = self.wxHierarchies()["wxType"]
try:
index1 = list.index(val1)
except:
index1 = len(list)-1
try:
index2 = list.index(val2)
except:
index2 = len(list)-1
if index1 < index2:
return -1
if index1 == index2:
return 0
if index1 > index2:
return 1
def getDominant(self, hierarchy, val1, val2):
# Return the value that appears first in the given hierarchy
list = self.wxHierarchies()[hierarchy]
index1 = list.index(val1)
index2 = list.index(val2)
if index1 < index2:
return val1
else:
return val2
def getCoverage(self, tree, node, subkey, covDescs, wxDef, attrList):
if subkey is None:
return None
cov = subkey.coverage()
if cov == "<NoCov>":
return ""
cov = wxDef.coverageDesc(subkey.wxType(), cov).lower()
for des_cov, des_type, des_inten, des_attr, desc in covDescs:
if self.matchSubkey(subkey, des_cov, des_type, des_inten, des_attr, attrList) == 1:
cov = self.getWxDesc(tree, node, subkey, desc)
return cov
def matchSubkey(self, subkey, cov, wxType, inten, attr, attrList):
if cov != "*":
if subkey.coverage() != cov:
return 0
if wxType != "*":
if subkey.wxType() != wxType:
return 0
if inten != "*":
if subkey.intensity() != inten:
return 0
if attr != "*":
if attr not in attrList:
return 0
return 1
def getWxDesc(self, tree, node, subkey, desc):
if type(desc) is types.MethodType:
return desc(tree, node, subkey)
else:
return desc
# VISIBILITY
def visibility_phrase(self):
return {
"setUpMethod": self.visibility_setUp,
"wordMethod": self.visibility_words,
"phraseMethods": [
#self.preProcessWx,
self.combinePhraseStats,
self.combineWords,
self.fillNulls,
self.timeDescriptorModeration,
self.assembleSubPhrases,
self.postProcessPhrase,
],
}
def visibility_setUp(self, tree, node):
elementInfoList = [self.ElementInfo("Wx", "List", self.WEATHER())]
self.subPhraseSetUp(tree, node, elementInfoList, self.visConnector)
node.set("combineVisibility", 1)
descriptor = self.phrase_descriptor(tree, node, "Visibility", "Visibility")
node.set("descriptor", descriptor)
return self.DONE()
def visibility_words(self, tree, node):
# Return a phrase for the given subPhrase
# Create a phrase to describe a list of weather subkeys for one sub-period
statDict = node.getStatDict()
rankList = self.getStats(statDict, "Wx")
if rankList is None or len(rankList) == 0:
return self.setWords(node, "")
# Filter rankList so we don't report visibility for weather subkeys
# not reported in the text (e.g. SChc, Iso)
rankList = self.checkPoP(tree, node, rankList)
subkeyList = self.getSubkeys(rankList)
lowVisNM = self.getVis(subkeyList)
if lowVisNM is None:
return self.setWords(node, "null")
# If less than null_nlValue (in nautical miles) return "null"
nullVisNM = self.null_nlValue(tree, node, "Visibility", "Visibility")
lowVisNM = self.roundStatistic(tree, node, lowVisNM, "Visibility")
if lowVisNM >= self.nlValue(nullVisNM, lowVisNM):
return self.setWords(node, "null")
words = self.nlValue(self.visibility_phrase_nlValue(tree, node), lowVisNM)
# See if the Wx type is significant
significantKeys = self.significant_wx_visibility_subkeys(tree, node)
sigFlag = self.findSubkeys(subkeyList, significantKeys)
# If there are no Wx obstructions for which we should report vsby
if sigFlag == 0:
words = ""
return self.setWords(node, words)
def visibility_phrase_nlValue(self, tree, node):
# Visibility descriptions for visibility_phrase.
# "Visibility less than 1 nautical mile then 2 NM in the afternoon."
# The numerical ranges are in nautical miles.
outUnits = self.element_outUnits(tree, node, "Visibility", "Visibility")
if outUnits == "NM":
return {
(0, 1): "1 NM or less",
(1.1, 2): "2 NM",
(2.1, 3): "3 NM",
(3.1, 4): "4 NM",
(4,1, 5): "5 NM",
(5.1, 6): "6 NM",
"default": "null",
}
else:
return {
(0, .3): "one quarter mile or less at times",
"default": "null",
}
# Handling visibility within the weather phrase
def embedded_visibility_flag(self, tree, node):
# If 1, report visibility embedded with the
# weather phrase. Set this to 0 if you are using the
# visibility_phrase.
return 0
def visibility_wx_threshold(self, tree, node):
# Weather will be reported if the visibility is below
# this threshold (in NM) OR if it includes a
# significant_wx_visibility_subkey (see below)
return None
def significant_wx_visibility_subkeys(self, tree, node):
# Weather values that constitute significant weather to
# be reported regardless of visibility.
# If your visibility_wx_threshold is None, you do not need
# to set up these subkeys since weather will always be
# reported.
# Set of weather key search tuples in the form:
# (cov type inten)
# Wildcards are permitted.
return [("* F "), ("* ZF "), ("* IF "), ("* H"), ("* K"), ("* BS"), ("* BD"), ("* VA")]
def visibility_weather_phrase_nlValue(self, tree, node):
# Visibility descriptions within the weather_phrase
# "Rain showers and fog with visibility less than 1 nautical mile in the morning."
# The numerical ranges are in nautical miles.
outUnits = self.element_outUnits(tree, node, "Visibility", "Visibility")
if outUnits == "NM":
return {
(0, 1): "visibility 1 NM or less",
(1, 2.1): "2 NM visibility",
(2.1, 3.1): "3 NM visibility",
(3.1, 4.1): "4 NM visibility",
(4.1, 5.1): "5 NM visibility",
(5.1, 6.1): "6 NM visibility",
"default": "null",
}
else:
return {
(0, .3): "visibility one quarter mile or less at times",
"default": "null",
}
def matchToWx(self, tree, node, element, timeRange=None, areaLabel=None,
algorithm=None, increment=None):
if timeRange is None:
timeRange = node.getTimeRange()
if areaLabel is None:
areaLabel = node.getAreaLabel()
algorithm, increment, noPrecipValue, percentThreshold, wxTypes = \
self.getMatchToWxInfo(tree, node, element, algorithm, increment)
#print "\nin matchToWx", element, timeRange, areaLabel
#print " ", algorithm, increment, noPrecipValue, wxTypes
#print " node", node.getAncestor("name")
#import traceback
#traceback.print_stack(limit=6)
# Gather all data that might be necessary
analysisMethodVal = tree.stats.get(
element, timeRange, areaLabel, mergeMethod="Max")
elementBins = tree.stats.get(
element, timeRange, areaLabel, statLabel="binnedPercent",
mergeMethod="MergeBins")
# Compute result
result = None
if algorithm == "AnalysisMethod":
#print " Returning AnalysisMethod", elementVal
result = analysisMethodVal
else:
# Determine "highKey" -- key with the highest coverage
highKey = self.getHighSubkey(tree, node, timeRange, areaLabel, wxTypes)
#print "highKey", highKey
# Handle case of no precipitating weather
if highKey is None:
result = self.getNoPrecipValue(
noPrecipValue, elementBins, analysisMethodVal)
else:
# Get the Element range of values corresponding to the
# high key coverage.
coverage = highKey.coverage()
method = getattr(self, f"coverage{element}_value")
elementRange = method(coverage)
if type(elementRange) is types.MethodType:
covLowVal, covHighVal = elementRange(tree, node, highKey)
else:
covLowVal, covHighVal = elementRange
if algorithm == "Max" or algorithm == "Mode":
# Merge the binned values over space and time
if elementBins is None:
return None
# Return the result
result = self.getBinnedResult(tree, node, elementBins, covLowVal,
covHighVal, increment, algorithm,
percentThreshold)
elif algorithm == "MaxMode":
# Get the list of bins for each grid in the time range
elementBinsList = tree.stats.get(
element, timeRange, areaLabel, statLabel="binnedPercent",
mergeMethod="List")
if elementBinsList is None or elementBinsList == []:
result = None
else:
result = self.getMaxModeResult(
tree, node, elementBinsList, covLowVal, covHighVal,
increment, algorithm, percentThreshold)
else:
log.warning(
"WARNING -- Invalid matToWxInfo algorithm for " + element + \
" Must be 'Max', 'Mode', 'MaxMode', or 'AnalysisMethod'")
return result
def getMatchToWxInfo(self, tree, node, element, algorithm, increment):
matchingInfo = self.matchToWxInfo(tree, node, element, element)
if matchingInfo == "":
log.warning(
"WARNING -- Cannot MatchToWx: Add " + element + \
" to matchToWxInfo_dict!!")
increment = 0
algorithm = "Max"
noPrecipValue = None
percentThreshold = 0
wxTypes = None
else:
# Get increment and algorithm if not set in arguments
# Get other matchingInfo
inc = matchingInfo[0]
alg = matchingInfo[1]
noPrecipValue = matchingInfo[2]
try:
percentThreshold = matchingInfo[3]
except:
percentThreshold = 0
try:
wxTypes = matchingInfo[4]
except:
wxTypes = None
if algorithm is None:
algorithm = alg
if increment is None:
increment = inc
return algorithm, increment, noPrecipValue, percentThreshold, wxTypes
def getNoPrecipValue(self, noPrecipValue, elementBins, analysisMethodVal):
if noPrecipValue is None:
result = None
elif noPrecipValue == "Max":
# Return the maximum value with > 0% areal coverage
if elementBins is None:
result = None
else:
elementVal = None
for lowVal, highVal, percent in elementBins:
if percent > 0:
elementVal = lowVal + increment
result = elementVal
elif noPrecipValue == "AnalysisMethod":
result = analysisMethodVal
else:
result = noPrecipValue
return result
def getBinnedResult(self, tree, node, elementBins, binLowVal,
binHighVal, increment, algorithm, percentThreshold):
# If algorithm == "Max":
# Return the MAXIMUM element value that falls within the element range
# AND has a greater than zero percentage.
# Else: (Algorithm == "Mode")
# Return the MOST FREQUENT element value that falls within the element range
# AND has a greater than zero percentage.
# We also calculate "resultValue" in case no element value falls within
# the range.
# If the algorithm is "Max", resultValue will be the Maximum element value
# overall, otherwise, it will be the Most Frequent.
elementVal = None
resultValue = None
# The Most Frequent value overall
maxPercent = 0
# The Most Frequent value within the lowVal/highVal range
maxInRangePercent = 0
#print "\nLooking for range", binLowVal, binHighVal
for lowVal, highVal, percent in elementBins:
#print "low, high, percent", lowVal, highVal, percent
#print " maxPercent, maxInRangePercent", maxPercent, maxInRangePercent
#print " elementVal, resultValue", elementVal, resultValue
if percent > percentThreshold:
# The element bins could be, for example, 55-65,
# so we add the increment
curValue = lowVal + increment
if algorithm == "Max":
# Bins are in ascending order so the
# (Maximum) will just be the last one
resultValue = curValue
if curValue >= binLowVal and curValue <= binHighVal:
elementVal = curValue
else: # "Mode"
if percent > maxPercent:
resultValue = curValue
maxPercent = percent
if curValue >= binLowVal and curValue <= binHighVal:
if percent > maxInRangePercent:
elementVal = curValue
maxInRangePercent = percent
#print "algorithm, elementVal, resultVal", algorithm, elementVal, resultValue
# If no element meets this criteria
# If the resultValue value is greater than the binHighVal, use binHighVal
# Otherwise, use the resultValue value
if elementVal is None:
if resultValue > binHighVal:
elementVal = binHighVal
else:
elementVal = resultValue
return elementVal
def getMaxModeResult(self, tree, node, elementBinsList, covLowVal,
covHighVal, increment, algorithm, percentThreshold):
# For each grid, find the Mode i.e. highest percentage value
# that falls within the coverage range
valueList = []
for elementBins in elementBinsList:
elementBins, timeRange = elementBins
elementVal = self.getBinnedResult(
tree, node, elementBins, covLowVal, covHighVal,
increment, "Mode", percentThreshold)
valueList.append(elementVal)
# Choose the maximum of these values that fall within the
# range for high key coverage.
#print "valueList", valueList
maxVal = valueList[0] # At least we'll have something ??
for value in valueList:
if value >= covLowVal and value <=covHighVal:
if value > maxVal:
maxVal = value
return maxVal
def getHighSubkey(self, tree, node, timeRange, areaLabel, wxTypes=None):
# Find the highest precip subkey in the ranked list
# If wxType is not None, consider only keys of that wxType
highKey = None
wxStats = tree.stats.get("Wx", timeRange, areaLabel)
#print "\nWx in getHighSubkey", wxStats
if wxStats is not None:
highKey = None
highCov = None
for subkeys, tr in wxStats:
#print "subkeys, rank", subkeys, tr
if subkeys is None:
continue
for subkey, rank in subkeys:
#print "subkey", subkey
if wxTypes is not None:
if subkey.wxType() not in wxTypes:
continue
else:
# Check for PoP-related
if self.pop_related_flag(tree, node, subkey):
# Do not consider trace events for matching
if subkey.wxType() in ['L', 'ZL'] or \
(subkey.wxType() in ['RW', 'SW'] and
subkey.intensity() == '--'):
# Move on to next subkey
continue
else:
# If non-precipitating, skip
continue
if highKey is None or \
self.dominantCoverageOrder(
subkey, highKey) == -1:
highKey = subkey
return highKey