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

1277 lines
50 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.
#
# ConfigVariables.py
# ConfigVariables for Text Products.
#
# Author: hansen
#
#
# SOFTWARE HISTORY
#
# Date Ticket# Engineer Description
# ------------- -------- --------- --------------------------------------------
# Mar 15, 2020 20234 NFTF Use ApparentT instead of WindChill/HeatIndex.
# Nov 17, 2020 8284 randerso Added "otherwise" value in
# maxReported_threshold_dict
# ----------------------------------------------------------------------------
##
# This is a base file that is not intended to be overridden.
##
import math
import TextUtils
class ConfigVariables(TextUtils.TextUtils):
def __init__(self):
TextUtils.TextUtils.__init__(self)
def missingData_notification_list(self, tree, node):
# Return a list of weather element names for which you wish
# to be notified when there is missing data
return []
# Descriptors: Phrase, Unit, Time periods
# Connecting phrases
def phrase_descriptor_dict(self, tree, node):
# Dictionary of descriptors for various weather elements in phrases
# The value for an element may be a phrase or a method
# If a method, it will be called with arguments:
# tree, node, key, element
return {
"HeatIndex": "heat index readings",
"PoP": "chance of",
#"PoP": self.areal_or_chance_pop_descriptor,
#"PoP": self.allAreal_or_chance_pop_descriptor,
"Period":"period",
"IceAccum": "ice accumulation",
"NewIceAccum": "new ice accumulation",
"SnowLevel": "snow level",
"Swell": "swell",
"TotalSnow": "total snow accumulation",
"NewTotalSnow": "total new snow accumulation",
"StormTotalSnow": "storm total snow accumulation",
"Visibility": "visibility",
"Wind": "winds",
"Wind20ft": "winds",
"WindChill": "wind chill readings",
"WindGust": "gusts up to",
"Wx": "",
#
# Snow accumulation combinations
"Snow": "snow accumulation",
"SnowSnow": "snow accumulation",
"SnowSleet": "snow and sleet accumulation",
#"SnowSleetIceCrystal": "snow and sleet and ice crystal accumulation",
"SnowSleetIceCrystal": "snow and sleet accumulation",
"Sleet": "sleet accumulation",
#"SleetIceCrystal": "sleet and ice crystal accumulation",
"SleetIceCrystal": "sleet accumulation",
#"IceCrystal": "ice crystal accumulation",
#"SnowIceCrystal": "snow and ice crystal accumulation",
"IceCrystal": "snow accumulation",
"SnowIceCrystal": "snow accumulation",
"New": "new",
#
"evening temperatures": "evening temperatures",
"highs": "highs",
"lakeWinds": "caution advised on area lakes",
"lows": "lows",
"severeWeather": "some thunderstorms may be severe",
"thunderstorms": "some thunderstorms may produce",
"heavyRainfall": "locally heavy rainfall possible",
"heavyRain" : "rain may be heavy at times",
"heavyRains" : "locally heavy rain possible",
"heavySnow" : "snow may be heavy at times",
"heavyPrecip" : "precipitation may be heavy at times",
"temperature":"temperature",
"higher":{
"MaxT":"warmer",
"MinT":"warmer",
"MinRH":"wetter",
"MaxRH":"wetter",
"RH":"wetter",
},
"lower":{
"MaxT":"cooler",
"MinT":"cooler",
"MinRH":"drier",
"MaxRH":"drier",
"RH":"drier",
},
# Marine
"WaveHeight": "waves", # Used for NSH/GLF
"seas": "combined seas",
"waves": "wind waves",
"inland waters": "waves",
"chop": "bay and inland waters",
"mixed swell": "mixed swell",
"dominant period": "dominant period", # to be used in combined seas phrase
"hurricane force winds to": "hurricane force winds to",
"storm force winds to": "storm force winds to",
"gales to": "gales to",
"up to": "up to",
# Fire Weather labels
"Sky/weather.........":"Sky/weather.........",
" 24 hr trend......":" 24 hr trend......",
"unchanged":"unchanged",
"missing":"MISSING",
"MinT":"lows",
"MaxT":"highs",
"MinT_FireWx": "Min temperature.....",
"MaxT_FireWx": "Max temperature.....",
"MinRH_FireWx":"Min humidity........",
"MaxRH_FireWx":"Max humidity........",
"Humidity recovery...":"Humidity recovery...",
"Upslope/downslope...":"Upslope/downslope...",
"20-foot winds.......":"20-foot winds.......",
" Valleys/lwr slopes...":" Valleys/lwr slopes...",
" Ridges/upr slopes....":" Ridges/upr slopes....",
"WIND................":"WIND................",
"LAL.................":"LAL.................",
"Free winds..........":"10K ft wind.........",
"Smoke dispersal.....":"Smoke dispersal.....",
"Transport winds.....":"Transport winds.....",
"Mixing height.......":"Mixing height.......",
"Haines Index........":"Haines Index........",
"CWR.................":"Chc of wetting rain.",
"Marine layer........":"Marine layer........",
# Used for Headlines
"EXPECTED" : "expected",
"IN EFFECT" : "in effect",
# Used for single values
"around": "around",
"through the day": "through the day",
"through the night": "through the night",
# Used for Tropical
"iminHR":"Hurricane conditions",
"iminTS":"Tropical storm conditions",
"iminTSposHR":"Tropical storm conditions with hurricane conditions possible",
"posTS":"Tropical storm conditions possible",
"posTSbcmgposHR":"Tropical storm conditions possible with hurricane conditions also possible",
"expTS":"Tropical storm conditions expected",
"posHR":"Hurricane conditions possible",
"expHR":"Hurricane conditions expected",
"expTSposHR":"Tropical storm conditions expected with hurricane conditions possible",
"posTSorHR":"Tropical storm or hurricane conditions possible" ,
}
def phrase_descriptor(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "phrase_descriptor_dict")
def phrase_connector_dict(self, tree, node):
# Dictionary of connecting phrases for various
# weather element phrases
# The value for an element may be a phrase or a method
# If a method, it will be called with arguments:
# tree, node
return {
# Used for Scalar and Wx elements
"then": {
"Sky": " then becoming ",
"otherwise": " then ",
},
# Used for Scalar and Vector elements
"increasing to": {
"Sky": " then becoming ",
"WaveHeight": " building to ",
"otherwise":" increasing to ",
},
"decreasing to": {
"Sky": " then becoming ",
"WaveHeight": " subsiding to ",
"otherwise":" decreasing to ",
},
"becoming": " becoming ",
"shifting to the": " shifting to the ",
# Used for Marine Vector weather elements
"rising to": " rising to ",
"easing to": " easing to ",
"backing": " backing ",
"veering":" veering ",
"becoming onshore": " becoming onshore",
}
def phrase_connector(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "phrase_connector_dict")
def useThenConnector_dict(self, tree, node):
# Conjunctive "THEN" to make 3+ subPhrase phrases
# flow better. e.g.
# "N WIND 10 TO 20 KT RISING TO 30 KT EARLY IN THE
# AFTERNOON, THEN RISING TO GALES TO 40 KT LATE
# IN THE AFTERNOON."
return {
"otherwise": 1,
"Wx": 0,
}
def useThenConnector(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "useThenConnector_dict")
def thenConnector_dict(self, tree, node):
# Conjunctive "THEN" to make 3+ subPhrase phrases
# flow better. e.g.
# "N WIND 10 TO 20 KT RISING TO 30 KT EARLY IN THE
# AFTERNOON, THEN RISING TO GALES TO 40 KT LATE
# IN THE AFTERNOON."
return {
"otherwise": ", then",
"Sky": "",
}
def thenConnector(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "thenConnector_dict")
def value_connector_dict(self, tree, node):
# Dictionary of connectors for ranges of values
# E.g. 25 TO 35 mph
# The value for an element may be a phrase or a method
# If a method, it will be called with arguments:
# tree, node
return {
"Period": " to ",
"Period2": " to ",
"TransWind": " to ",
"Wind20ft": " to ",
"FreeWind": " to ",
"Swell": " to ",
"Swell2": " to ",
"Wind": " to ",
"MaxT": "-",
"MinT": "-",
"MaxRH": "-",
"MinRH": "-",
"RH": "-",
"T": "-",
"WaveHeight" : " to ",
"WindWaveHgt" : " to ",
"WindChill": " to ",
"HeatIndex": " to ",
}
def value_connector(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value,
"value_connector_dict")
def units_descriptor_dict(self, tree, node):
# Dictionary of descriptors for various units
return {
"units": {
"ft": "feet",
"F":"",
"C":"degrees",
"K":"kelvins",
"%":" percent",
"in":"inches",
"kts":"knots",
"s":"seconds",
"hrs":"hours",
"m/s":"meters/second",
"mph":"mph",
"m":"meters",
"m^2/s":"meters^2/second",
"kt-ft":"knots-feet",
"mm":"millimeters",
"degrees": "degrees",
"percent": "percent",
},
"unit": {
"ft":"foot",
"F":"",
"C":"degree",
"K":"kelvin",
"%":" percent",
"in":"inch",
"kts":"knot",
"s":"second",
"hrs":"hour",
"m/s":"meter/second",
"mph":"mph",
"m":"meter",
"m^2/s":"meter^2/second",
"kt-ft":"knot-foot",
"mm":"millimeter",
"degree": "degree",
"percent": "percent",
},
}
def units_descriptor(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "units_descriptor_dict")
def element_inUnits_dict(self, tree, node):
# Dictionary of descriptors for various units
return {
"QPF":"in",
"Wind":"kts",
"Wind20ft":"kts",
"Wx":"wx",
"SnowAmt":"in",
"IceAccum":"in",
"StormTotalSnow": "in",
"PoP":"%",
"Sky":"%",
"FzLevel":"ft",
"SnowLevel":"ft",
"RH":"%",
"HeatIndex":"F",
"WindChill":"F",
"T":"F",
"Td":"F",
"MinT":"F",
"MaxT":"F",
"WindWaveHgt":"ft",
"WaveHeight":"ft",
"Swell":"ft",
"Swell2":"ft",
"Period":"s",
"Period2":"s",
"WindGust":"kts",
"LAL":"cat",
"CWR":"%",
"Haines":"cat",
"MixHgt":"ft",
"FreeWind":"kts",
"TransWind":"kts",
"Stability":"cat",
"HrsOfSun":"hrs",
"MarineLayer":"ft",
"InvBurnOffTemp":"F",
"VentRate":"kt-ft",
"MinRH": "%",
"MaxRH":"%",
"RH":"%",
"WetFlag":"y/n",
"Ttrend":"F",
"RHtrend":"%",
}
def element_inUnits(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "element_inUnits_dict")
def element_outUnits_dict(self, tree, node):
# Dictionary of descriptors for various units
return {
"QPF":"in",
"Wind":"kts",
"Wind20ft":"kts",
"Wx":"wx",
"SnowAmt":"in",
"IceAccum":"in",
"StormTotalSnow": "in",
"PoP":"%",
"Sky":"%",
"FzLevel":"ft",
"SnowLevel":"ft",
"RH":"%",
"HeatIndex":"F",
"WindChill":"F",
"T":"F",
"Td":"F",
"MinT":"F",
"MaxT":"F",
"WindWaveHgt":"ft",
"WaveHeight":"ft",
"Swell":"ft",
"Swell2":"ft",
"Period":"s",
"Period2":"s",
"WindGust":"kts",
"LAL":"cat",
"CWR":"%",
"Haines":"cat",
"MixHgt":"ft",
"FreeWind":"kts",
"TransWind":"kts",
"Stability":"cat",
"HrsOfSun":"hrs",
"MarineLayer":"ft",
"InvBurnOffTemp":"F",
"VentRate":"kt-ft",
"MinRH": "%",
"MaxRH":"%",
"RH":"%",
"WetFlag":"y/n",
"Ttrend":"F",
"RHtrend":"%",
"Visibility": "SM", # statute miles -- Can also be NM (nautical miles)
}
def element_outUnits(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "element_outUnits_dict")
############################################
### GLOBAL THRESHOLDS AND VARIABLES
def maxReported_threshold_dict(self, tree, node):
# Winds will not be reported above this value:
# For example, if set to 30, all winds above 30 will
# be reported as:
# "Winds up to 30 knots."
return {
"Wind": 200, # knots or mph depending on product
"Wind20ft": 200, # knots or mph depending on product
"otherwise": math.inf, # default to no max threshold
}
def maxReported_threshold(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "maxReported_threshold_dict")
def range_nlValue_dict(self, tree, node):
# If the range of values less than this amount, return as a single value
#
return {
"MaxT": 5,
"MinT": 5,
"MinRH": 5,
"MaxRH": 5,
"RH": 5,
}
def range_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "range_nlValue_dict")
def maximum_range_nlValue_dict(self, tree, node):
# Maximum range to be reported within a phrase
# e.g. 5 to 10 mph
# Units depend on the product
return {}
## return {
## "Wind": 10,
## "Wind20ft": 10,
## "TransWind": 10,
## "FreeWind": 10,
## "Swell": 5,
## "Swell2": 5,
## "WaveHeight": 2,
## "WindWaveHgt": 2,
## "MaxT": 50,
## "MinT": 50,
## "HeatIndex": 50,
## "WindChill": 50,
## "MaxRH": 50,
## "MinRH": 50,
## "RH": 50,
## }
def maximum_range_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "maximum_range_nlValue_dict")
def minimum_range_nlValue_dict(self, tree, node):
# This threshold is the "smallest" min/max difference allowed between values reported.
# For example, if threshold is set to 5 for "MaxT", and the min value is 45
# and the max value is 46, the range will be adjusted to at least a 5 degree
# range e.g. 43-48. These are the values that are then submitted for phrasing
# such as:
# HIGHS IN THE MID 40S
return {
## "Wind": 0,
## "Wind20ft": 0,
## "TransWind": 0,
## "FreeWind": 0,
## "Swell": 0,
## "Swell2": 0,
## "WaveHeight": 0,
## "WindWaveHgt": 0,
## "MaxT": 0,
## "MinT": 0,
## "MaxRH": 0,
## "MinRH": 0,
## "RH": 0,
"WindChill": 10,
"HeatIndex": 5,
}
def minimum_range_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "minimum_range_nlValue_dict")
def range_bias_nlValue_dict(self, tree, node):
# "Min", "Average", "Max"
# Should the range be taken from the "min" "average" or "max"
# value of the current range?
return {"otherwise": "Average"}
def range_bias_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "range_bias_nlValue_dict")
def maximum_range_bias_nlValue_dict(self, tree, node):
# "Min", "Average", "Max"
# Should the maximum_range be taken from the "min" "average" or "max"
# value of the current range?
return {
"ApparentT": {(-100.0, 50.0): "Min",
(50.0, 80.0): "Average",
(80.0, 150.0): "Max",
'default': "Max",
},
"Wind": "Max",
"otherwise": "Average",
}
def maximum_range_bias_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "maximum_range_bias_nlValue_dict")
def minimum_range_bias_nlValue_dict(self, tree, node):
# "Min", "Average", "Max"
# Should the minimum_range be taken from the "min" "average" or "max"
# value of the current range?
return {
"ApparentT": {(-100.0, 50.0): "Min",
(50.0, 80.0): "Average",
(80.0, 150.0): "Max",
'default': "Max",
},
"Wind": "Max",
"MaxRH": "Max",
"MinRH": "Max",
"RH": "Max",
"otherwise": "Average",
}
def minimum_range_bias_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "minimum_range_bias_nlValue_dict")
def highValue_threshold_dict(self, tree, node):
# If high wind conditions report both "becoming" and
# "increasing/decreasing"
# Southeast winds around 70 mph becoming south
# and increasing to around 105 mph
# Otherwise, it will just be reported with "increasing"
# or "becoming":
# Southeast winds around 20 mph becoming south
# around 15 mph
return {
"Wind": 45, # knots or mph depending on product
"Wind20ft": 45, # knots or mph depending on product
"TransWind": 45,
"FreeWind": 45,
"Swell": 20,
"Swell2": 20,
"otherwise": 45,
}
def highValue_threshold(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "highValue_threshold_dict")
#####
# NULL value phrases
def first_null_phrase_dict(self, tree, node):
# Phrase to use if values THROUGHOUT the period or
# in the first period are Null (i.e. below threshold OR NoWx)
# E.g. LIGHT WINDS. or LIGHT WINDS BECOMING N 5 MPH.
return {
"Wind": "light winds",
"Wind20ft": "light winds",
"TransWind": "light winds",
"FreeWind": "light winds",
"Swell": "light swells",
"Swell2": "",
"Wx": "",
"WindGust": "",
"WaveHeight": "waves 2 feet or less",
"WindWaveHgt": "waves 2 feet or less",
"CWR": "",
}
def first_null_phrase(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "first_null_phrase_dict")
def null_phrase_dict(self, tree, node):
# Phrase to use for null values in subPhrases other than the first
# Can be an empty string
# E.g. "NORTH WINDS 20 to 25 KNOTS BECOMING LIGHT"
return {
"Wind": "light",
"Wind20ft": "light",
"TransWind": "light",
"FreeWind": "light",
"Swell": "light",
"Swell2": "",
"Wx":"",
"WindGust": "",
"WaveHeight": "2 feet or less",
"WindWaveHgt": "2 feet or less",
"CWR": "",
}
def null_phrase(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "null_phrase_dict")
############################################
### GLOBAL SCALAR THRESHOLDS AND VARIABLES
def scalar_difference_nlValue_dict(self, tree, node):
# Scalar difference. If the difference between scalar values
# for 2 sub-periods is greater than or equal to this value,
# the different values will be noted in the phrase.
return {
"WindGust": 10, # knots or mph depending on product
"Period": 5, # seconds
# Set PoP to high value so we will never have sub-phrases
"PoP": 200, # percentage
"WaveHeight": 5, # feet
"WindWaveHgt": 5, # feet
"LAL": 1,
"MaxT": 10,
"MinT": 10,
"MaxRH": 10,
"MinRH": 10,
"RH": 10,
"Ttrend": 5,
"RHtrend": 10,
"MixHgt": 2000,
"VentRate": 50000,
"CWR": 10,
"WindChill": 10,
"HeatIndex": 20,
"SnowLevel": 1000,
"otherwise": 10,
}
def scalar_difference_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "scalar_difference_nlValue_dict")
### PoP
def pop_lower_threshold(self, tree, node):
# Pop values below this amount will not be reported
return self.popThreshold(tree, node, self._hoursSChcEnds, 15.0, 25.0)
def popThreshold(self, tree, node, hoursSChcEnds, schcVal, defaultVal):
hours = self.hoursPastProductStart(tree, node)
dur = node.getTimeRange().duration() // 3600
val = defaultVal
if dur <= 12:
if hours < hoursSChcEnds:
val = 15.0
elif hours+dur <= hoursSChcEnds:
val = 15.0
return val
def pop_upper_threshold(self, tree, node):
# Pop values above this amount will not be reported
return 100
def lowPop_flag(self, tree, node, threshold):
# Checks pop against the given threshold and returns 1 if
# it is below that threshold.
# If there is no data, return None
# Otherwise, return 0
popStats = self.matchToWx(tree, node, "PoP")
if popStats is None:
return None
if popStats < threshold:
return 1
else:
return 0
############################################
### GLOBAL VECTOR THRESHOLDS AND VARIABLES
def null_nlValue_dict(self, tree, node):
# Threshold below which values are considered "null" and
# reported using te null_phrase (see above)
return {
"otherwise": 0,
"Wind": 5,
"Wind20ft": 5,
"TransWind": 0,
"FreeWind": 0,
"Swell": 5, # ft
"Swell2": 5, # ft
"Visibility": .3, # in nautical miles. Report if less than this value.
"WaveHeight": 3, # ft
"WindWaveHgt": 3, # ft
"WindGust": 20,
"LAL": 0,
"MaxT": 0,
"MinT": 0,
"MaxRH": 0,
"MinRH": 0,
"RH": 0,
"Ttrend": 0,
"RHtrend": 0,
"MixHgt": 0,
"VentRate": 0,
"CWR": 0,
"Ttrend": 0,
"RHtrend": 0,
"PoP": 0,
"WindChill": -100,
"HeatIndex": 108,
"SnowLevel": 0,
}
def null_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "null_nlValue_dict")
def null_alwaysDistinct_flag_dict(self, tree, node):
# If 1, null values will always be considered distinct from non-null values
# when combining subphrases. Thus, with a null value of 5,
# you may end up with phrases such as:
# Winds less than 5 mph becoming east 5 mph.
# If set to 0, the determination will be made based on the scalar or
# vector difference as with non-null values.
# (See scalar_difference_nlValue_dict and vector_mag_difference_nlValue_dict)
return {
"otherwise": 1,
#"Wind": 0,
}
def null_alwaysDistinct_flag(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "null_alwaysDistinct_flag_dict")
def increment_nlValue_dict(self, tree, node):
# Increment for rounding values
# Units depend on the product
return {
"otherwise": 1,
"CWR": 10,
"PoP": 10,
"TransWind": 5,
"FreeWind": 5,
"MarineLayer": 100,
"MixHgt": 100,
"SnowLevel": 100,
"SnowAmt": .1,
"StormTotalSnow": .1,
"IceAccum": .1,
"Visibility": .25,
"Wind": 5,
"Wind20ft": 5,
"WindGust": 5,
"VentRate": 100,
"QPF": .01,
}
def increment_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "increment_nlValue_dict")
def rounding_method_dict(self, tree, node):
# Special rounding methods
#
# Uncomment Wind method for marine products
return {
#"Wind": self.marineRounding,
}
def rounding_method(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value,"rounding_method_dict", execMethods=0)
def adjust_method_dict(self, tree, node):
# Special conversion methods
#
# Used in Fire Weather product
return {
#"Wind": self._adjustWind,
}
def adjust_method(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "adjust_method_dict", execMethods=0)
def marineRounding(self, value, mode, increment, maxFlag):
# Rounding for marine winds
mode = "Nearest"
if maxFlag:
if value > 30 and value < 34:
mode = "RoundDown"
elif value > 45 and value < 48:
mode = "RoundDown"
else:
mode = "Nearest"
return self.round(value, mode, increment)
def vector_mag_difference_nlValue_dict(self, tree, node):
# Replaces WIND_THRESHOLD
# Magnitude difference. If the difference between magnitudes
# for sub-ranges is greater than or equal to this value,
# the different magnitudes will be noted in the phrase.
# Units can vary depending on the element and product
return {
"Wind": 10,
"Wind20ft": 10,
"TransWind": 10,
"FreeWind": 10,
"Swell": 5, # ft
"Swell2": 5, # ft
"otherwise": 5,
}
def vector_mag_difference_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "vector_mag_difference_nlValue_dict")
def vector_dir_difference_dict(self, tree, node):
# Replaces WIND_DIR_DIFFERENCE
# Direction difference. If the difference between directions
# for sub-ranges is greater than or equal to this value,
# the different directions will be noted in the phrase.
# Units are degrees
return {
"Wind": 60, # degrees
"Wind20ft": 60, # degrees
"TransWind": 60, # mph
"FreeWind": 60, # mph
"Swell":60, # degrees
"Swell2":60, # degrees
"otherwise": 60,
}
def vector_dir_difference(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "vector_dir_difference_nlValue_dict")
def vector_dir_difference_nlValue_dict(self, tree, node):
dict = self.vector_dir_difference_dict(tree, node)
# If you want to use a nlValue for the Wind direction
# override this method and uncomment the entry below for Wind.
# Adjust the values given to the desired values.
# The Wind direction threshold will then be chosen according to
# the MINIMUM nlValue determined from the Wind MAGNITUDE.
# See 'checkVectorDifference' (PhraseBuilder) for usage.
#
# dict["Wind"] = {
# (0, 10): 60,
# (10, 200): 40,
# 'default': 40,
# }
# When the wind is between 0 and 10 kt,
# report when the wind direction change is 60 degrees or more
#
return dict
def vector_dir_difference_nlValue(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "vector_dir_difference_nlValue_dict")
def embedded_vector_descriptor_flag_dict(self, tree, node):
# If set to 1, the descriptor will be embedded in the phrase:
# such as "North winds 20 to 25 knots becoming light and variable"
# instead of "Winds north 20 to 25 knots becoming light and variable"
return {
"Wind": 1,
"Wind20ft": 1,
"TransWind": 1,
"FreeWind": 1,
"Swell": 1,
"Swell2": 1,
}
def embedded_vector_descriptor_flag(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value,
"embedded_vector_descriptor_flag_dict")
# Sea Breeze information
def seaBreeze_thresholds(self, tree, node):
# Offshore and Onshore wind directions to identify sea breezes
#
# Offshore wind direction boundaries in first sub-period
offshore1 = 30
offshore2 = 150
# Onshore wind direction boundaries in second sub-period
onshore1 = 330
onshore2 = 210
return offshore1, offshore2, onshore1, onshore2
def seaBreeze_areaLabels(self, tree, node):
# Return the Offshore and Onshore local effect area labels
# These areas must be set up as "intersectAreas" in the
# Component definition for the seaBreezeFlag to function
return "OffShoreArea", "OnShoreArea"
# TRENDS
def trend_threshold_dict(self, tree, node):
return {
"MinT": 1,
"MaxT": 1,
"MinRH": 1,
"MaxRH": 1,
"RH": 1,
}
def trend_threshold(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "trend_threshold_dict")
# COMBINING
def combine_singleValues_flag_dict(self, tree, node):
# Dictionary of weather elements to combine using single values
# rather than ranges. If you are using single value statistics
# for a weather element, you will want to set this flag to 1.
# If there is no entry for an element, min/max combining will
# be done.
# The value for an element may be a phrase or a method
# If a method, it will be called with arguments:
# tree, node
return {}
def combine_singleValues_flag(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value,
"combine_singleValues_flag_dict")
# UNTIL PHRASING
def untilPhrasing_flag_dict(self, tree, node):
# If set to 1, "until" time descriptor phrasing will be used.
# E.g. "NORTH WINDS 20 MPH UNTIL 10 AM, THEN 35 MPH"
return {
"otherwise": 0,
#"Wind" : 1,
}
def untilPhrasing_flag(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "untilPhrasing_flag_dict")
def onTheFly_untilPhrasing_flag_dict(self, tree, node):
# If set to 1, "until" time descriptor phrasing will be used ONLY if
# the time range for a sub-phrase does not end on a 3-hour boundary.
return {
"otherwise": 0,
#"Wind" : 1,
}
def onTheFly_untilPhrasing_flag(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "onTheFly_untilPhrasing_flag_dict")
def untilPhrasing_format_dict(self, tree, node):
# Format for "until" time descriptors.
# If "military": UNTIL 1000
# If "standard": UNTIL 10 AM
return {
"otherwise": "military",
#"Wind": "standard",
}
def untilPhrasing_format(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "untilPhrasing_format_dict")
### REMOVING EMPTY INDENTED PHRASES
def removeEmptyPhrase(self, tree, node):
# If an indented phrase is empty, do not include the entry for it
return 0
### PERIOD COMBINING
def periodCombining_elementList(self, tree, node):
# Weather Elements to determine whether to combine periods
return ["Sky", "Wind", "Wx", "PoP", "MaxT", "MinT"]
# Marine
#return ["WaveHeight", "Wind", "Wx"]
# Diurnal Sky Wx pattern
#return ["DiurnalSkyWx"]
def periodCombining_startHour(self, tree, node):
# Hour after which periods may be combined
return 36
# Automatic Collapsing of Sub-phrases for Combined periods
def collapseSubPhrase_hours_dict(self, tree, node):
# If the period is longer than these hours, subphrases will automatically
# be collapsed.
return {
"otherwise": 23,
#"Wx": 12,
}
def collapseSubPhrase_hours(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "collapseSubPhrase_hours_dict")
def mergeMethod_dict(self, tree, node):
# Designates the mergeMethod to use when sub-phrases are automatically collapsed.
return {
"otherwise": "MinMax",
"PoP": "Max",
"Wx": "Average",
}
def mergeMethod(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "mergeMethod_dict")
def nextDay24HourLabel_flag(self, tree, node):
# Return 1 to have the TimeDescriptor module label 24 hour periods starting
# after 1600 as the next day.
# This is needed for the Fire Weather Extended product,
# but not for other products when period combining.
# NOTE: If you are doing period combining, you should
# set this flag to zero and set the "splitDay24HourLabel_flag" to 1.
return 0
def splitDay24HourLabel_flag(self, tree, node):
# Return 0 to have the TimeDescriptor module label 24 hour periods
# with simply the weekday name (e.g. Saturday)
# instead of including the day and night periods
# (e.g. Saturday and Saturday night)
# NOTE: If you set this flag to 1, make sure the "nextDay24HourLabel_flag"
# is set to zero.
# NOTE: This applied only to periods that are exactly 24-hours in length.
# Periods longer than that will always be split into day and night labels
# (e.g. SUNDAY THROUGH MONDAY NIGHT)
return 0
def mostImportant_dict(self, tree, node):
# Can be set to "Min" or "Max". Works for Scalar or Vector elements.
# Only the most important sub-phrase will be reported
# using the "mostImportant_descriptor" (see below).
# For example, instead of:
# WIND CHILL READINGS 5 BELOW TO 15 BELOW ZERO IN THE EARLY
# MORNING INCREASING TO ZERO TO 10 BELOW ZERO IN THE AFTERNOON
# we will report
# LOWEST WIND CHILL READINGS 5 BELOW TO 15 BELOW IN THE EARLY
# MORNING
return {
"otherwise": None,
"WindChill": "Min",
}
def mostImportant(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "mostImportant_dict")
def mostImportant_descriptor_dict(self, tree, node):
return {
"otherwise": None,
"WindChill": "lowest wind chill readings",
}
def mostImportant_descriptor(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "mostImportant_descriptor_dict")
def matchToWxInfo_dict(self, tree, node):
# The system will automatically match the following elements to
# the highest ranking weather subkey coverage.
# Each entry is a tuple of (increment, algorithm, noPrecipValue) where
# increment: This is the increment from the low "bin" value
# to be added. For example, PoP has a bin of 55-65, so
# its increment is 5 to end up with values as multiples of 10.
# algorithm: Can be
# Max: The MAXIMUM value that falls within the coverage range
# for the highest ranking subkey will be chosen.
# Mode: The MOST FREQUENT (over space and time) value that
# falls within the coverage range for the highest ranking
# subkey will be chosen.
# MaxMode: This is the MAXIMUM value over time of the MOST
# FREQUENT values over area for each of the grids in the timeRange.
# In other words, for each grid, we find the Mode i.e. MOST FREQUENT
# value that falls within the coverage range for the highest
# ranking subkey. Then we find the MAXIMUM of these values
# over the grids again falling within the coverage values.
# NOTE:: If you use MaxMode, you will want to make sure the
# PoP "binnedPercent" resolution is high e.g. [3] so that
# low PoP's covering extended time periods do not wipe out
# higher PoP's covering shorter time periods.
# AnalysisMethod: This will simply use whatever analysis method
# is specified as the first entry in the product component
# for the element. For example, if you have
#
# ("PoP", self.stdDevMaxAvg, [3]),
# ("PoP", self.binnedPercent, [3]),
#
# the "stdDevMaxAvg" method will be used.
#
# noPrecipValue: The value that should be returned if there is
# no precipitating weather. Can be:
# None
# an actual data value
# "Max": The maximum value found that has a greater > 0% occurrence.
# "AnalysisMethod": As above, will return the result of the product
# component analysis method e.g. stdDevMaxAvg or maximum.
#
# percentThreshold: (optional) Percent of areal coverage a value must have in
# order to be considered for the element value returned.
# Default is 0.
#
# wxTypes filter: (optional) Match only to weather keys of these wxTypes e.g.
# match LAL only to "T"
#
# EXAMPLE 1: Suppose we have:
# Wx Hours 1-12: Chc R (coverage range is 30-60)
# PoP Hours 1-3: 40% (over 70% of area), 50% (over 30% of area)
# Hours 4-12: 30
# For the 12-hour PoP,
# If set to Max, we will get PoP: 50
# If set to Mode, we will get PoP: 30
# If set to MaxMode, we will get PoP: 40
# For the Hours 1-3 PoP:
# If set to Max, we will get PoP: 50
# If set to Mode, we will get PoP: 40
# If set to MaxMode, we will get PoP: 40
# NOTE: IF you add a new element to this list, you MUST include
# a coverage table named "coverage<elementName>_value". Follow
# the example for "coveragePoP_value" in CommonUtils. You can
# then access the element value by calling "matchToWx" (WxPhrases).
#
return {
"PoP": (5, "Max", None, 0),
# If there's no precipitating weather, return LAL 1
"LAL": (0, "Max", 1, 0, ["T"]),
}
def matchToWxInfo(self, tree, node, key, value):
return self.access_dictionary(tree, node, key, value, "matchToWxInfo_dict")
def alphabetizeHeaders(self):
# Set to 1 if you want the zones and cities of area headers to be alphabetized
return 0
## LOCAL EFFECTS FOR HEADLINES can be handled using the hazard_hook method which
## returns a string to be inserted in the headline (just before the ...) for clarification e.g.
## "in the mountains", "for lower elevations"
## ...WINTER STORM WARNING IN EFFECT UNTIL 1 AM EST TUESDAY IN THE MOUNTAINS...
## NOTE: There is a limited set of words that must begin your returned string e.g.
## IN, ABOVE, BELOW, NEAR, FOR, AROUND, DUE, ALONG
## 1. Override the "Hazards" product component (DiscretePhrases) to sample the Hazards grid
## (independent of normal Hazards Table processing) and set up "intersectAreas"
## and/or "additionalAreas".
## For example:
## def Hazards(self):
## return {
## "type": "component",
## "lineLength": 66,
## "methodList": [
## self.assembleChildWords,
## self.wordWrap,
## ],
## "analysisList":[
## ("Hazards", self.dominantDiscreteValue),
## ],
## "phraseList":[
## self.hazards_phrase,
## ],
## "autoSentence": 0,
## "intersectAreas": [
## ("Hazards", ["AboveElev", "BelowElev"]),
## ],
## }
## 2. Override the "hazard_hook" method and check for local effects. Arguments are:
## hazardPhen: hazard phenomenon e.g. WS
## hazardSig: hazard significance e.g. W (watch, warning, advisory)
## hazardAct: hazard action code e.g. "NEW", "EXA", "EXB", "EXT", "UPG", "CAN", "CON", "EXP"
## hazardStart and hazardEnd: start and end times for the hazard
## For example:
## def hazard_hook(self, tree, node, hazardPhen, hazardSig, hazardAct,
## hazardStart, hazardEnd):
## hazTR = self.makeTimeRange(hazardStart, hazardEnd)
## if hazardSig != "":
## phenSig = hazardPhen + "." + hazardSig
## else:
## phenSig = hazardPhen
## # Check for hazards AboveElev and BelowElev
## aboveName = self.getIntersectName(node.getAreaLabel(), "AboveElev")
## aboveHazards = tree.stats.get("Hazards", hazTR, aboveName)
## #print "Above", aboveHazards
## foundAbove = 0
## if aboveHazards is not None:
## for hazards, tr in aboveHazards:
## if phenSig in hazards:
## foundAbove = 1
## break
## belowName = self.getIntersectName(node.getAreaLabel(), "BelowElev")
## belowHazards = tree.stats.get("Hazards", hazTR, belowName)
## #print "Below", belowHazards
## foundBelow = 0
## if belowHazards is not None:
## for hazards, tr in belowHazards:
## if phenSig in hazards:
## foundBelow = 1
## break
## # Determine if local effect wording is appropriate
## if foundAbove and not foundBelow:
## return "in the mountains"
## if foundBelow and not foundAbove:
## return "for lower elevations"
## return ""
def hazard_hook(self, tree, node, hazardPhen, hazardSig, hazardAct, hazardStart, hazardEnd):
return ""
########### LOCAL EFFECTS ######################
#### Component-Level Local Effect thresholds
def repeatingEmbedded_localEffect_threshold(self, tree, component):
# Number of embedded local effect phrases allowed in a component
# before they are gathered together into a conjunctive local
# effect clause. For example, with the threshold set to 2:
#
# Instead of:
# Cloudy windward and partly cloudy leeward.
# Rain likely windward and scattered showers leeward.
# Chance of precipitation 50 percent windward and 30
# percent leeward.
#
# We will produce:
# Windward...Cloudy...Rain likely...Chance of precipitation 50 percent.
# Leeward...Partly cloudy...Scattered showers...Chance of precipitation
# 30 percent.
#
# NOTE: If we have even one conjunctive local effect, however, all will be left
# conjunctive. For example, instead of:
#
# Cloudy windward and partly cloudy leeward.
# Windward...Rain likely in the morning.
# Leeward...Scattered showers in the afternoon.
#
# We will produce:
# Windward...Cloudy...Rain likely in the morning.
# Leeward...Partly cloudy...Scattered showers in the afternoon.
#
return 2
def repeatingPhrase_localEffect_threshold(self, tree, component):
# NOT YET IMPLEMENTED
#
# Number of repeating local effect phrases allowed inside a
# set of conjunctive local effects for each of the
# repeatingPhrase_categories (see below).
#
# For example, with the default of 1 and the categories below,
#
# Instead of:
# Chance of thunderstorms in the morning.
# Windward...Cloudy...Rain likely...Chance of precipitation 70 percent.
# Leeward...Partly cloudy...Scattered showers...Chance of precipitation 30
# percent. Highs in the 40s. Winds 20 mph.
#
# We will produce:
# Windward...Cloudy....Rain likely...Chance of thunderstorms in the morning...
# Chance of precipitation 70 percent.
# Leeward...Partly cloudy...Scattered showers...Chance of thunderstorms in
# the morning...Chance of precipitation 30 percent. Highs in the 40s.
# Winds 20 mph.
#
# Note that if we had:
# Windward...Cloudy....Rain likely...Chance of precipitation 70 percent.
# Leeward...Partly cloudy...Scattered showers......Chance of precipitation
# 30 percent. Highs in the 40s. Winds 20 mph.
#
# The phrasing would remain unchanged since there are 2 phrases (Temps and Winds)
# in the "ALL OTHER PHRASES" category that would have to be repeated within the
# conjunctive local effects.
return 1
def repeatingPhrase_localEffect_categories(self, tree, component):
return [
["skyPopWx_phrase", "sky_phrase", "weather_phrase", "popMax_phrase"],
["ALL OTHER PHRASES"],
]
def lePhraseNameGroups(self, tree, component):
# Groups of phrase names that can be combined into embedded local effect phrases.
# If the phrase is not listed here, it is assumed that only phrases with the
# same name can be combined with it into an embedded local effect phrase.
# For example:
# With the group: ("skyPopWx_phrase", "weather_phrase"), we will allow:
#
# A 20 percent chance of rain windward and areas of fog leeward.
#
# Since "skyPopWx_phrase" and "wind_phrase" do not appear as group, we will
# not allow:
#
# A 20 percent chance of rain windward and north winds 20 mph leeward.
#
#
return [
("skyPopWx_phrase", "weather_phrase", "sky_phrase", "popMax_phrase",
"fireSky_phrase")
]