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

563 lines
23 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.
#
# MarinePhrases.py
# Methods for producing text forecast from SampleAnalysis statistics.
#
# Author: hansen
# ----------------------------------------------------------------------------
##
# This is a base file that is not intended to be overridden.
##
import ScalarPhrases
import VectorRelatedPhrases
import WxPhrases
import DiscretePhrases
import re
class MarinePhrases(ScalarPhrases.ScalarPhrases, VectorRelatedPhrases.VectorRelatedPhrases,
WxPhrases.WxPhrases, DiscretePhrases.DiscretePhrases):
def __init__(self):
ScalarPhrases.ScalarPhrases.__init__(self)
VectorRelatedPhrases.VectorRelatedPhrases.__init__(self)
WxPhrases.WxPhrases.__init__(self)
DiscretePhrases.DiscretePhrases.__init__(self)
############################################
### MARINE PHRASES
def marine_wind_flag(self, tree, node):
# If 1, Wind wording will reflect the
# crossing of significant thresholds such as gales.
# E.g. "West gales to 35 knots." instead of "West winds 35 knots."
return 0
def marine_wind_combining_flag(self, tree, node):
# If 1, Wind combining will reflect the
# crossing of significant thresholds such as gales.
# E.g. "HURRICANE FORCE WINDS TO 100 KNOTS." instead of
# "NORTH HURRICANE FORCE WINDS TO 100 KNOTS EASING TO
# HURRICANE FORCE WINDS TO 80 KNOTS IN THE AFTERNOON."
return 0
def marine_wind_verbose_flag(self, tree, node):
# Applies only if marine_wind_flag is 1 and the
# and marine_wind_combining_flag is 0.
# If 1, Wind phrasing will repeat special descriptors to produce:
# "NORTH HURRICANE FORCE WINDS TO 100 KNOTS EASING TO
# HURRICANCE FORCE WINDS TO 80 KNOTS IN THE AFTERNOON."
# If 0, will produce:
# "NORTH HURRICANE FORCE WINDS TO 100 KNOTS EASING TO
# 80 KNOTS IN THE AFTERNOON."
#
return 1
def marine_wind_phrase(self):
return {
"setUpMethod": self.marine_wind_setUp,
"wordMethod": self.vector_words,
"phraseMethods": self.standard_vector_phraseMethods(),
}
def marine_wind_withGusts_phrase(self):
return {
"setUpMethod": self.marine_wind_withGusts_setUp,
"wordMethod": self.vector_words,
"phraseMethods": self.standard_vector_phraseMethods(),
}
def marine_wind_setUp(self, tree, node):
return self.wind_setUp(tree, node, gustFlag=0,
connectorMethod=self.marine_vectorConnector)
def marine_wind_withGusts_setUp(self, tree, node):
return self.wind_setUp(tree, node, gustFlag=1,
connectorMethod=self.marine_vectorConnector)
def noWaveHeight_phrase(self, tree, node, elementName1, elementName2=""):
if elementName1 != elementName2:
elementNames = elementName1 + " or " + elementName2
else:
elementNames = elementName1
return "|* Insufficient grids for " + elementNames + " during " + \
str(node.getTimeRange())+ "*|"
def waveHeight_wind_threshold(self, tree, node):
# Wind value above which waveHeight (combined seas)
# is reported vs. wind waves.
# Also, the Swell phrase is omitted if this threshold is exceeded.
# Unit is knots
return 34
def inlandWatersAreas(self, tree, node):
# List of edit area names that are inland or bay waters
# as opposed to "seas"
# The phrasing for these areas will be treated differently
# (see the wave_phrase)
#
# e.g.
# return ["TampaBayWaters"]
return []
def inlandWatersWave_element(self, tree, node):
# Weather element first and second choice to use for reporting inland waters waves
# If there is incomplete or no data for the first element, the second will be used.
return ("WindWaveHgt", "WaveHeight")
def combinedSeas_threshold(self, tree, node):
# See wave_phrase
# If waves and swells are above this threshold,
# combined seas will be reported AND no Swell phrase will be reported.
# Units: feet
return 7
def seasWaveHeight_element(self, tree, node):
# Weather element to use for reporting seas waves
# IF above wind or swell thresholds
return "WaveHeight"
def seasWindWave_element(self, tree, node):
# Weather element to use for reporting seas waves
# IF above wind or swell thresholds
return "WindWaveHgt"
### WaveHeight and WindWaveHgt
def wave_withPeriods_phrase(self):
return {
"setUpMethod": self.wave_withPeriods_setUp,
"wordMethod": self.wave_words,
"phraseMethods": self.standard_phraseMethods()
}
def wave_phrase(self):
return {
"setUpMethod": self.wave_setUp,
"wordMethod": self.wave_words,
"phraseMethods": self.standard_phraseMethods()
}
def wave_withPeriods_setUp(self, tree, node):
return self.wave_setUp(tree, node, periodFlag=1)
def wave_setUp(self, tree, node, periodFlag=0):
areaLabel = node.getAreaLabel()
timeRange = node.getTimeRange()
inlandWaters = self.inlandWatersAreas(tree, node)
if self.currentAreaContains(tree, inlandWaters) == 1:
elementName, elementName2 = self.inlandWatersWave_element(tree, node)
statsByRange = tree.stats.get(elementName, timeRange, areaLabel, mergeMethod="List")
if statsByRange is None:
elementName = elementName2
# Do not report Period for inland waters
periodFlag = 0
descriptor = self.phrase_descriptor(tree, node, "inland waters", elementName)
node.set("descriptor", descriptor)
elif self.seasFlag(tree, node):
# Use wave height elementName (default)
elementName = self.seasWaveHeight_element(tree, node)
descriptor = self.phrase_descriptor(tree, node, "seas", elementName)
node.set("descriptor", descriptor)
else:
# Use wind waves (default)
elementName = self.seasWindWave_element(tree, node)
periodFlag = 0
descriptor = self.phrase_descriptor(tree, node, "waves", elementName)
node.set("descriptor", descriptor)
wave = self.ElementInfo(elementName, "List")
elementInfoList = [wave]
if periodFlag:
node.set("periodFlag", 1)
period = self.ElementInfo("Period", "Average", primary=0)
elementInfoList.append(period)
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
return self.DONE()
def seasFlag(self, tree, node):
# Return 1 if we are to report combined seas
timeRange = node.getTimeRange()
areaLabel = node.getAreaLabel()
winds = tree.stats.get("Wind", timeRange, areaLabel, mergeMethod="Max")
if winds is None:
return 0
maxWind, dir = winds
# Determine if we will report combined seas OR wind waves
seasFlag = 0
if maxWind > self.waveHeight_wind_threshold(tree, node):
seasFlag = 1
else:
swell = tree.stats.get("Swell", timeRange, areaLabel, mergeMethod="Max")
swell2 = tree.stats.get("Swell2", timeRange, areaLabel, mergeMethod="Max")
maxWave = tree.stats.get("WindWaveHgt", timeRange, areaLabel, mergeMethod="Max")
if swell is None or maxWave is None:
pass # Leave seasFlag at zero
else:
# We'll decide to report combined seas by looking at
# the MAX of waves and swells over the entire time period
swells, dir = swell
if swell2 is None:
swells2 = 0
else:
swells2, dir = swell2
threshold = self.combinedSeas_threshold(tree, node)
if maxWave > threshold and \
(swells > threshold or swells2 > threshold):
seasFlag = 1
return seasFlag
def wave_words(self, tree, node):
# Return a phrase for wave and optionally Period for the given subPhrase
elementInfo = node.getAncestor("firstElement")
elementName = elementInfo.name
statDict = node.getStatDict()
if statDict is None:
return self.setWords(node,"")
wave = self.getStats(statDict, elementName)
if wave is None:
return self.setWords(node, "")
minimum, maximum = self.getValue(wave, "MinMax")
threshold = self.nlValue(self.null_nlValue(
tree, node, elementName, elementName), maximum)
if int(minimum) < threshold and int(maximum) < threshold:
return self.setWords(node, "null")
waveStr = self.getScalarRangeStr(tree, node, elementName, minimum, maximum)
units = self.units_descriptor(tree, node, "units", "ft")
waveUnit = self.units_descriptor(tree, node, "unit", "ft")
if int(minimum) == 1 and int(maximum) == 1:
units = waveUnit
words = waveStr + " " + units
if "Period" in statDict:
period = self.getStats(statDict, "Period")
if period is not None:
avg = self.getValue(period, "Average")
periodUnits = self.units_descriptor(tree, node, "units", "s")
periodUnit = self.units_descriptor(tree, node, "unit", "s")
avg = int(avg)
if avg == 1:
periodUnits = periodUnit
periodDescriptor = self.phrase_descriptor(
tree, node, "dominant period", elementName)
words = words + " " + periodDescriptor + " " + repr(avg) + " " + periodUnits
return self.setWords(node, words)
def waveHeight_phrase(self):
return {
"setUpMethod": self.waveHeight_setUp,
"wordMethod": self.waveHeight_words,
"phraseMethods": self.standard_phraseMethods()
}
def waveHeight_setUp(self, tree, node):
elementInfoList = [self.ElementInfo("WaveHeight", "List")]
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
return self.DONE()
def waveHeight_words(self, tree, node):
"Create phrase for waves"
statDict = node.getStatDict()
stats = self.getStats(statDict, "WaveHeight")
if stats is None:
nodataPhrase = self.noWaveHeight_phrase(
tree, node, "WaveHeight", "WaveHeight")
return self.setWords(node.parent, nodataPhrase)
minimum, maximum = self.getValue(stats, "MinMax")
avg = (minimum + maximum)/2
words = self.wave_range(avg)
return self.setWords(node, words)
def wave_range(self, avg):
# Make wave ranges based off the average wave value
table = ((0, "less than 1 foot"), (1, "1 foot or less"),
(1.5, "1 to 2 feet"), (2, "1 to 3 feet"),
(3, "2 to 4 feet"), (4, "3 to 5 feet"),
(5, "3 to 6 feet"), (6, "4 to 7 feet"),
(7, "5 to 8 feet"), (8, "6 to 10 feet"),
(10, "8 to 12 feet"), (12, "10 to 14 feet"),
(14, "12 to 16 feet"), (18, "14 to 18 feet"),
(20, "15 to 20 feet"), (100, "over 20 feet"))
waveRange = ""
for rangeMax, desc in table:
if avg <= rangeMax:
waveRange = desc
break
return waveRange
### Chop
def chop_phrase(self):
return {
"setUpMethod": self.chop_setUp,
"wordMethod": self.chop_words,
"phraseMethods": self.standard_phraseMethods()
}
def chop_setUp(self, tree, node):
# Only generate this phrase for inland waters areas
inlandWaters = self.inlandWatersAreas(tree, node)
if self.currentAreaContains(tree, inlandWaters) == 0:
return self.setWords(node, "")
# Set up for only one subPhrase.
chop = self.ElementInfo("Wind", "Max", self.VECTOR())
# Uncomment the following line if you want the chop_phrase to
# have subPhrases e.g. "A light chop in morning."
#chop = self.ElementInfo("Wind", "List", self.VECTOR())
elementInfoList = [chop]
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
descriptor = self.phrase_descriptor(tree, node, "chop", "chop")
node.set("descriptor", descriptor)
return self.DONE()
def chop_words(self, tree, node):
"Create phrase for chop"
statDict = node.getStatDict()
stats = self.getStats(statDict, "Wind")
if stats is None:
return self.setWords(node, "")
maxWind, dir = self.getValue(stats, "Max", self.VECTOR())
if maxWind <= 7:
value = "smooth"
elif maxWind > 7 and maxWind <= 12:
value = "a light chop"
elif maxWind > 12 and maxWind <= 17:
value = "a moderate chop"
elif maxWind > 17 and maxWind <= 22:
value = "choppy"
elif maxWind > 22 and maxWind <= 27:
value = "rough"
elif maxWind > 27 and maxWind <= 32:
value = "very rough"
elif maxWind > 32:
value = "extremely rough"
else:
value = "!!!Chop phrase problem!!!"
return self.setWords(node, value)
### Swell
def swell_phrase(self):
return {
"setUpMethod": self.swell_setUp,
"wordMethod": self.swell_words,
"phraseMethods": self.standard_vector_phraseMethods(),
}
def swell_withPeriods_phrase(self):
return {
"setUpMethod": self.swell_withPeriods_setUp,
"wordMethod": self.swell_words,
"phraseMethods": self.standard_vector_phraseMethods(),
}
def swell_withPeriods_setUp(self, tree, node):
return self.swell_setUp(tree, node, periodFlag=1)
def swell_setUp(self, tree, node, periodFlag=0):
# Do not report swells for inland waters
inlandWaters = self.inlandWatersAreas(tree, node)
if self.currentAreaContains(tree, inlandWaters) == 1:
return self.setWords(node, "")
# Do not report swells if we are reporting combined seas
if self.seasFlag(tree, node) == 1:
return self.setWords(node, "")
swell = self.ElementInfo("Swell", "List", self.VECTOR())
elementInfoList = [swell]
if periodFlag:
swellPhrase = self.swell_withPeriods_phrase
else:
swellPhrase = self.swell_phrase
swell2 = self.ElementInfo(
"Swell2", "MinMax", self.VECTOR(), phraseDef=swellPhrase) #, primary=0)
elementInfoList = [swell, swell2]
if periodFlag:
node.set("periodFlag", 1)
period = self.ElementInfo("Period", "MinMax", primary=0)
period2 = self.ElementInfo("Period2", "MinMax", primary=0)
elementInfoList.append(period)
elementInfoList.append(period2)
self.subPhraseSetUp(tree, node, elementInfoList, self.vectorConnector)
return self.DONE()
def swell_words(self, tree, node):
# Create phrase for swell for a given set of stats in statsByRange
#print "\n in swell words"
periodFlag = node.getAncestor("periodFlag")
statDict = node.getStatDict()
#Check for Swell alone
swell2 = self.getStats(statDict, "Swell2")
if swell2 is None:
oneSwell = 1
else:
oneSwell = 0
# Swell and Swell2 subPhrases
subPhraseParts = []
elementInfoList = node.getAncestor("elementInfoList")
for swell, period in [("Swell", "Period"), ("Swell2", "Period2")]:
if swell == "Swell":
checkRepeating = 1
else:
checkRepeating = 0
for elementInfo in elementInfoList:
if elementInfo.name == swell:
swellInfo = elementInfo
break
swellWords = self.simple_vector_phrase(tree, node, swellInfo, checkRepeating)
if swellWords == "null" or swellWords == "":
subPhraseParts.append("")
continue
# Add Period
periodPhrase = ""
if periodFlag == 1:
periodStats = self.getStats(statDict, period)
periodPhrase = self.embedded_period_phrase(tree, node, periodStats)
swellWords = swellWords + periodPhrase
subPhraseParts.append(swellWords)
#print "swell", node.getTimeRange(), subPhraseParts
if subPhraseParts[0] != "" and subPhraseParts[1] != "":
words = subPhraseParts[0] + " and " + subPhraseParts[1]
# Check for mixed swell on first subPhrase
if node.getIndex() == 0:
mixedSwell = self.checkMixedSwell(tree, node, statDict)
if mixedSwell:
mixedSwellDesc = self.phrase_descriptor(tree, node, "mixed swell", "Swell")
phrase = node.getParent()
phrase.set("descriptor", mixedSwellDesc)
phrase.doneList.append(self.embedDescriptor)
elif subPhraseParts[0] != "":
words = subPhraseParts[0]
elif subPhraseParts[1] != "":
words = subPhraseParts[1]
else:
words = "null"
return self.setWords(node, words)
def checkMixedSwell(self, tree, node, statDict):
# Check for mixed swell wording
# Return mixed swell phrase if appropriate
# Otherwise, return None
swell = self.getStats(statDict, "Swell")
swell2 = self.getStats(statDict, "Swell2")
if swell is None or swell2 is None:
return 0
swellMag, swellDir = swell
swell2Mag, swell2Dir = swell2
swellMag = self.getValue(swellMag)
swell2Mag = self.getValue(swell2Mag)
if self.direction_difference(swellDir, swell2Dir) >= 90.0 and \
swellMag > 0 and \
swell2Mag / swellMag > 0.50:
return 1
else:
return 0
### Period
def embedded_period_phrase(self, tree, node, periodStats):
# Create a period phrase to be embedded with a Swell phrase
if periodStats is None:
return ""
period = int(self.getValue(periodStats))
outUnits = self.element_outUnits(tree, node, "Period", "Period")
units = self.units_descriptor(tree, node, "units", outUnits)
unit = self.units_descriptor(tree, node, "unit", outUnits)
if period == 1:
units = unit
return " at " + repr(period) + " " + units
def period_phrase(self):
return {
"setUpMethod": self.period_setUp,
"wordMethod": self.period_words,
"phraseMethods": self.standard_phraseMethods()
}
def period_setUp(self, tree, node):
elementInfoList = [self.ElementInfo("Period", "List")]
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
return self.DONE()
def period_words(self, tree, node):
# Return a phrase for Period for the given index in statsByRange
statDict = node.getStatDict()
stats = self.getStats(statDict, "Period")
if stats is None:
return self.setWords(node, "")
periodValue = int(self.getValue(stats))
outUnits = self.element_outUnits(tree, node, "Period", "Period")
units = self.units_descriptor(tree, node, "units", outUnits)
unit = self.units_descriptor(tree, node, "unit", outUnits)
if periodValue == 1:
units = unit
return self.setWords(node, repr(periodValue) + " " + units)
def marine_abbreviateText(self, fcst):
#add a space at the beginning to create a word boundary on the first word
#(space is removed at end of method).
fcst = " " + fcst
fcst = re.sub(r'\n', r' ',fcst)
fcst = re.sub(r'(?i)(\W)NORTH(\W)', r'\1N\2',fcst)
fcst = re.sub(r'(?i)(\W)SOUTH(\W)', r'\1S\2',fcst)
fcst = re.sub(r'(?i)(\W)EAST(\W)', r'\1E\2',fcst)
fcst = re.sub(r'(?i)(\W)WEST(\W)', r'\1W\2',fcst)
fcst = re.sub(r'(?i)(\W)NORTHEAST(\W)', r'\1NE\2',fcst)
fcst = re.sub(r'(?i)(\W)SOUTHEAST(\W)', r'\1SE\2',fcst)
fcst = re.sub(r'(?i)(\W)SOUTHWEST(\W)', r'\1SW\2',fcst)
fcst = re.sub(r'(?i)(\W)NORTHWEST(\W)', r'\1NW\2',fcst)
fcst = re.sub(r'(?i)(\W)KNOTS?(\W)', r'\1kt\2',fcst)
## fcst = re.sub(r'(?i)(\W)FOOT(\W)', r'\1FT\2',fcst)
fcst = re.sub(r'(?i)(\W)FEET(\W)', r'\1ft\2',fcst)
fcst = re.sub(r'(\W)Position(\W)', r'\1Psn\2',fcst)
fcst = re.sub(r'(\W)position(\W)', r'\1psn\2',fcst)
fcst = re.sub(r'(\W)Visibility(\W)', r'\1Vsby\2',fcst)
fcst = re.sub(r'(\W)visibility(\W)', r'\1vsby\2',fcst)
fcst = re.sub(r'(\W)Thunderstorm', r'\1Tstm',fcst)
fcst = re.sub(r'(\W)thunderstorm', r'\1tstm',fcst)
fcst = re.sub(r'(\W)Average(\W)', r'\1Avg\2',fcst)
fcst = re.sub(r'(\W)average(\W)', r'\1avg\2',fcst)
fcst = re.sub(r'(?i)(\W)NAUTICAL MILES?(\W)', r'\1nm\2',fcst)
fcst = re.sub(r'(?i)(\W)ATLANTIC(\W)', r'\1Atlc\2',fcst)
fcst = re.sub(r'(?i)(\W)FATHOMS?(\W)', r'\1fm\2',fcst)
fcst = re.sub(r'(?i)(\W)LONGITUDE(\W)', r'\1Long\2',fcst)
fcst = re.sub(r'(?i)(\W)PACIFIC(\W)', r'\1Pac\2',fcst)
fcst = re.sub(r'(?i)(\W)DEGREES?(\W)', r'\1deg\2',fcst)
fcst = re.sub(r'(?i)(\W)MILLIBARS?(\W)', r'\1mb\2',fcst)
fcst = re.sub(r'(\W)Pressure(\W)', r'\1Pres\2',fcst)
fcst = re.sub(r'(\W)pressure(\W)', r'\1pres\2',fcst)
fcst = re.sub(r'(?i)(\W)(SUN)DAY(\W)', r'\1\2\3',fcst)
fcst = re.sub(r'(?i)(\W)(MON)DAY(\W)', r'\1\2\3',fcst)
fcst = re.sub(r'(?i)(\W)(TUE)SDAY(\W)', r'\1\2\3',fcst)
fcst = re.sub(r'(?i)(\W)(WED)NESDAY(\W)', r'\1\2\3',fcst)
fcst = re.sub(r'(?i)(\W)(THU)RSDAY(\W)', r'\1\2\3',fcst)
fcst = re.sub(r'(?i)(\W)(FRI)DAY(\W)', r'\1\2\3',fcst)
fcst = re.sub(r'(?i)(\W)(SAT)URDAY(\W)', r'\1\2\3',fcst)
fcst = re.sub(r'^ ', r'',fcst)
return fcst