# ---------------------------------------------------------------------------- # 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. # # OFF_NH2_NT4_Overrides # # Author: # ---------------------------------------------------------------------------- # TAFB Change Log # 05/03/11 - added mergeProds for previous text functionality (CNJ/JL) # 05/03/11 - removed duplicate postProcessProduct (CNJ/JL) # 05/03/11 - added allowedHazards to enable local expected headlines wording (CNJ/JL) # 05/03/11 - override postProcessPhrase to deal with expected wording (CNJ/JL) # 05/03/11 - override actionControlWord to remove "IN EFFECT" from all headlines (CNJ/JL) # 05/05/11 - change phraseList in OFFPeriod to use wave_WithPeriods (CNJ/JL) # 05/05/11 - changed wave_setUp to key on swell instead of period (CNJ/JL) # 05/05/11 - major changes to wave_words, merged in parts of swell_words (CNJ/JL) # 05/05/11 - copy of simple_vector_phrase with magnitude values turned off (CNJ/JL) # 05/05/11 - changed phraseList and analysisList in OFFExtended to allow swell wording (CNJ/JL) # 05/05/11 - override vector_winds to set avgwnd global variable (CNJ/JL) # 05/05/11 - created FDS wind/swell table in null_nlValue_tafb_dict override (CNJ/JL) # 05/05/11 - added debugging print statements to generateForecast (CNJ/JL) # 05/05/11 - added re.subs to postProcessPhrase to format warning headlines in TAFB style (CNJ/JL) # 06/03/11 - commented out MethodList under OFFPeriod (PJS/JL) # 06/03/11 - changed PhraseList in OFFPeriod to self.marine_wind_withGusts_phrase,self._windLocalEffects_list (PJS/JL) # 08/01/11 - headline replacement for storm warning affecting tropical storm warning (JL) # 08/16/11 - adjust Local Effect Wording (EC/JL) # 08/24/11 - Swell wording commented out along with waves with periods phrase for tropical formatter to work (JL) # 09/04/11 - Turned on Period Combining for Include Tropical (JL) # ---------------------------------------------------------------------------- import OFF_NH2_NT4 import string, time, re, os, types, copy import TextRules, SampleAnalysis import ForecastNarrative class OFF_NH2_NT4_Overrides: def __init__(self): pass ##### # Include the list of forecaster names to the initial pop-up box # This is used later for adding the names at the end of the product # F. Achorn/OPC 02/04/11 # Modified by J. Lewitsky/NHC 02/04/11 ##### #class TextProduct(TextRules.TextRules, SampleAnalysis.SampleAnalysis): VariableList = [ (("Include Tropical?", "includeTropical") , "Yes", "radio", ["Yes","No"]), (("Forecaster Name", "forecasterName") , "99", "radio", ["WALLY BARNES","NELSON","STRIPLING","SCHAUER","CHRISTENSEN", "LEWITSKY", "AL", "RUBIO", "AGUIRRE", "DGS", "MUNDELL", "COBB", "LANDSEA", "CAB", "WALLACE", "FORMOSA", "HUFFMAN", "MT", "GARCIA", "WALTON"]), (("Period Combining?","pdCombo"), "Yes", "radio", ["Yes","No"]), ] # added below section to enable previous text functionality # 05/03/11 CNJ/JL # Set descriptive label to use in VariableList mergeCwfText="""\ Generate new text for this number of periods and use previous OFF for all later periods. Selecting zero will keep all old text but will refresh headers.""" mergeCwfEntry = ((mergeCwfText,'updatePeriodIndex'), "No old text", "radio", ["No old text", 0,1,2,3,4]) VariableList.append(mergeCwfEntry) def __init__(self): TextRules.TextRules.__init__(self) SampleAnalysis.SampleAnalysis.__init__(self) ######################################################################## # OVERRIDING METHODS, THRESHOLDS AND VARIABLES ######################################################################## # MUST BE OVERRIDDEN IN LOCAL FILE def _Text1(self): return "OFFSHORE WATERS FORECAST FOR THE GULF OF MEXICO\n\n" + \ "SEAS GIVEN AS SIGNIFICANT WAVE HEIGHT...WHICH IS THE AVERAGE\n" + \ "HEIGHT OF THE HIGHEST 1/3 OF THE WAVES. INDIVIDUAL WAVES MAY BE\n" + \ "MORE THAN TWICE THE SIGNIFICANT WAVE HEIGHT.\n\n" def _Text2(self, argDict): synopsis = "" # Try to get Synopsis from previous OFF ## if self._definition["synopsisUGC"] == "AMZ089": productID = "MIAOFFNT4" synopsis = self.getPreviousProduct(productID, "SYNOPSIS") # Clean up the previous synopsis synopsis = re.sub(r'\n', r' ', synopsis) synopsis = self._synopsisHeading + synopsis synopsis = self.endline(synopsis, linelength=66, breakStr=" ") # Convert absolute time pointer to a tuple of values like that # returned by time.gmtime() expTuple = time.strptime('%s' % (self._expireTime), '%b %d %y %H:%M:%S GMT') # Format expiration time for inclusion in synopsis header expTime = time.strftime('%d%H%M', expTuple) return "%s-%s-\n" % ("GMZ089", expTime) + \ "%s\n" % "SYNOPSIS FOR THE GULF OF MEXICO" + \ "%s\n" % self._timeLabel + "\n" + \ synopsis + "\n$$\n\n" ## def _Text3(self): ## synopsis2 = "" #### if self._definition["synopsis2UGC"] == "AMZ088": ## ## productID = "MIAOFFNT4" ## # Can't just search for "SYNOPSIS" ## # It will only return the first one it finds ## entire_product = string.strip(self.getPreviousProduct(productID)) ## # get just the second synopsis ## # split the product on the synopsisHeading (.SYNOPSIS...) ## # then split on "$$" and grab everything before the $$ at the end of the synopsis ## synopsis2 = entire_product.split(self._definition["synopsisHeading"])[2].split("$$")[0] ## ## # Clean up the previous synopsis ## synopsis2 = re.sub(r'\n', r' ', synopsis2) ## synopsis2 = self._synopsisHeading + synopsis2 ## synopsis2 = self.endline(synopsis2, linelength=66, breakStr=" ") ## ## # Convert absolute time pointer to a tuple of values like that ## # returned by time.gmtime() ## expTuple = time.strptime('%s' % (self._expireTime), ## '%b %d %y %H:%M:%S GMT') ## ## # Format expiration time for inclusion in synopsis header ## expTime = time.strftime('%d%H%M', expTuple) ## ## return "%s-%s-\n" % ("AMZ088", expTime) + \ ## "%s\n" % "SYNOPSIS FOR THE SW N ATLC INCLUDING THE BAHAMAS" + \ ## "%s\n" % self._timeLabel + "\n" + \ ## synopsis2 + "\n$$\n\n" ## #### expTuple = time.strptime('%s' % (self._expireTime), #### '%b %d %y %H:%M:%S GMT') #### #### # Format expiration time for inclusion in synopsis header #### expTime = time.strftime('%d%H%M', expTuple) #### #### return "%s-%s-\n" % (self._synopsis2UGC, expTime) + \ #### self._timeLabel + "\n\n" + \ #### self._synopsisHeading + "\n" + \ #### synopsis + "\n$$\n\n" ## # copy directly from OFF_TextProduct. only modify to add second synopsis def generateForecast(self, argDict): # Get variables error = self._getVariables(argDict) if error is not None: return error # Get the areaList -- derived from defaultEditAreas and # may be solicited at run-time from user if desired self._areaList = self.getAreaList(argDict) if len(self._areaList) == 0: return "WARNING -- No Edit Areas Specified to Generate Product." # Determine time ranges error = self._determineTimeRanges(argDict) if error is not None: return error # Sample the data error = self._sampleData(argDict) if error is not None: return error # Initialize the output string fcst = "" fcst = self._preProcessProduct(fcst, argDict) # Generate the product for each edit area in the list fraction = 0 fractionOne = 1.0/float(len(self._areaList)) percent = 50.0 self.setProgressPercentage(percent) # Need to know how many areas to process after this. # will insert second synopsis before the last fcst area areasLeft = len(self._areaList) - 1 for editArea, areaLabel in self._areaList: skipAreas = self._skipAreas(argDict) argDict["editArea"] = (editArea, areaLabel) if self.currentAreaContains(argDict, skipAreas): continue self.progressMessage(fraction, percent, "Making Product for " + areaLabel) fcst = self._preProcessArea(fcst, editArea, areaLabel, argDict) fcst = self._makeProduct(fcst, editArea, areaLabel, argDict) fcst = self._postProcessArea(fcst, editArea, areaLabel, argDict) # are we before the Atlantic zones? # if so, add synopsis2 if areasLeft == 9: fcst = fcst + self._Text3() fraction = fractionOne areasLeft = areasLeft - 1 # next four print lines added for debugging of shapefile problem - 05/05/11 print "##########################################################" print "EDIT AREA IS:" print areaLabel print "##########################################################" fcst = self._postProcessProduct(fcst, argDict) return fcst ######################################################################## #added by JRL 11/05 def gust_wind_difference_nlValue(self, tree, node): # Difference between gust and maxWind below which gusts are not # mentioned. Units are MPH if self._includeTropical: return 5 else: return 10 def temporalCoverage_hours(self, parmHisto, timeRange, componentName): # COMMENT: At WFO MFL we use 3 hrly wind grids. If you use 1 hrly wind grids # and this parameter is 2 or higher, tropical cyclone winds affecting the very # early or latter part of a forecast period might be neglected. 1 assures # maximum sensitivity. # CJ commented out ## if self._includeTropical: ## return 1 ## else: ## return 0 return 1 def temporalCoverage_hours_dict(self, parmHisto, timeRange, componentName): # This is the temporalCoverage_hours specified per weather element. # Used by temporalCoverage_flag return { "PoP": 2.0, "Wx": 2.0, "Wind": 3, "Swell": 3, "pws34": 4, "pws64": 4, "pwsD34": 4, "pwsN34": 4, "pwsD64": 4, "pwsN64": 4, } # SampleAnalysis overrides def moderated_dict(self, parmHisto, timeRange, componentName): # This dictionary defines the low and high limit at which # outliers will be removed when calculating moderated stats. # By convention the first value listed is the percentage # allowed for low values and second the percentage allowed # for high values. dict = SampleAnalysis.SampleAnalysis.moderated_dict(self, parmHisto, timeRange, componentName) if self._includeTropical: dict["Wind"] = (0, 15) dict["WindGust"] = (0, 15) dict["WaveHeight"] = (10, 10) dict["Swell"] = (0, 15) else: dict["WaveHeight"] = (10, 10) # dict["Wind"] = (0, 3) # dict["WaveHeight"] = (5,5) return dict def null_nlValue_dict(self, tree, node): # Threshold below which values are considered "null" and not reported. # Units depend on the element and product dict = TextRules.TextRules.null_nlValue_dict(self, tree, node) dict["WaveHeight"] = 2 dict["WindWaveHgt"] = 2 dict["Wind"] = 10 dict["WindGust"] = 120 dict["Swell"] = 1 dict["Visibility"] = 5 # in nautical miles. Report if less than this value. return dict # ConfigVariables Overrides def phrase_descriptor_dict(self, tree, node): # Descriptors for phrases dict = TextRules.TextRules.phrase_descriptor_dict(self, tree, node) dict["Wind"] = "winds" dict["WaveHeight"] = "seas" dict["seas"] = "seas" dict["mixed swell"] = "mixed swell" dict["waves"] = "seas" dict["dominant period"] = "dominant period" # Apply only if marine_wind_flag (see above) is set to 1: dict["hurricane force winds to"] = "hurricane force winds to" dict["storm force winds to"] = "storm force winds to" dict["gales to"] = "gales to" dict["up to"] = "LESS THAN" dict["around"] = "" # Used for Tropical dict["iminHR"] = "HURRICANE CONDITIONS" dict["iminTS"] = "TROPICAL STORM CONDITIONS" dict["iminTSposHR"] = "TROPICAL STORM CONDITIONS WITH HURRICANE CONDITIONS POSSIBLE" dict["posTS"] = "TROPICAL STORM CONDITIONS POSSIBLE" dict["posTSbcmgposHR"] = "TROPICAL STORM CONDITIONS POSSIBLE WITH HURRICANE CONDITIONS ALSO POSSIBLE" dict["expTS"] = "TROPICAL STORM CONDITIONS EXPECTED" dict["posHR"] = "HURRICANE CONDITIONS POSSIBLE" dict["expHR"] = "HURRICANE CONDITIONS EXPECTED" dict["expTSposHR"] = "TROPICAL STORM CONDITIONS EXPECTED WITH HURRICANE CONDITIONS POSSIBLE" dict["posTSorHR"] = "TROPICAL STORM OR HURRICANE CONDITIONS POSSIBLE" return dict 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. dict = TextRules.TextRules.first_null_phrase_dict(self, tree, node) dict["WaveHeight"] = "seas 2 ft or less" dict["WindWaveHgt"] = "seas 2 ft or less" dict["Wind"] = "VARIABLE WINDS LESS THAN 10 KT" dict["Swell"] = "" return 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" dict = TextRules.TextRules.null_phrase_dict(self, tree, node) dict["WaveHeight"] = "2 feet or less" dict["WindWaveHgt"] = "2 feet or less" dict["Wind"] = "VARIABLE WINDS LESS THAN 10 KT" dict["Wx"] = "" dict["Swell"] = "light" dict["hurricane force winds to"] = "hurricane force winds to" dict["storm force winds to"] = "storm force winds to" dict["gales to"] = "gales to" dict["up to"] = "" return dict # added by JRL 11/05 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 dict = TextRules.TextRules.maximum_range_nlValue_dict(self, tree, node) #----------------------------------------------------------------------- # COMMENT: Override max ranges for certain fields # This dict specifications allows for wind speed ranges of up to 20 mph # during tropical cyclone situations allowing for far better wind speed # phrases. #----------------------------------------------------------------------- if self._includeTropical: dict["Wind"] = {'default': 5, (0.0, 4.0): 0, (4.0, 33.0): 5, (33.0, 52.0): 10, (52.0, 200.0): 20, } else: dict["Wind"] = { (0,25):5, (25,50):10, (50,200):25, "default":10, } dict["WaveHeight"] = { (0,3):1, (3,7):2, (7,10):3, (10,20):5, (20,200):10, "default":5, } dict["Swell"] = 5 dict["Swell2"] = 5 #dict["WaveHeight"] = 2 dict["WindWaveHgt"] = 2 return 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: dict = TextRules.TextRules.minimum_range_nlValue_dict(self, tree, node) # HIGHS IN THE MID 40S if self._includeTropical: dict["WaveHeight"] = {(0,3):1, (3,7):2, (7,10):3, (10,20):5, (20,200):10, "default":5, } return 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 dict = TextRules.TextRules.phrase_connector_dict(self, tree, node) dict["rising to"] = { "Wind": "...INCREASING to ", "Swell": "...building to ", "Swell2": "...building to ", "WaveHeight": "...building to ", "WindWaveHgt": "...building to ", } dict["easing to"] = { "Wind": "...diminishing to ", "Swell": "...subsiding to ", "Swell2": "...subsiding to ", "WaveHeight": "...subsiding to ", "WindWaveHgt": "...subsiding to ", } dict["backing"] = { "Wind": "...becoming ", "Swell": "...becoming ", "Swell2": "...becoming ", "WaveHeight": "...becoming ", "WindWaveHgt": "...becoming ", } dict["veering"] = { "Wind": "...becoming ", "Swell": "...becoming ", "Swell2": "...becoming ", "WaveHeight": "...becoming ", "WindWaveHgt": "...becoming ", } dict["becoming"] = "...becoming " dict["increasing to"] = { "Wind": "...INCREASING to ", "Swell": "...building to ", "Swell2": "...building to ", "WaveHeight": "...building to ", "WindWaveHgt": "...building to ", } dict["decreasing to"] = { "Wind": "...diminishing to ", "Swell": "...subsiding to ", "Swell2": "...subsiding to ", "WaveHeight": "...subsiding to ", "WindWaveHgt": "...subsiding to ", } dict["shifting to the"] = "...shifting " dict["becoming onshore"] = " becoming onshore " dict["then"] = {"Wx": ". ", "Vector": "...becoming ", "Scalar": "...becoming ", "otherwise": "...becoming ", } return 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 ## dict = TextRules.TextRules.maximum_range_nlValue_dict(self, tree, node) ## dict["Wind"] = { ## (0,25):5, ## (25,50):10, ## (50,200):25, ## "default":10, ## } ## dict["Swell"] = 5 ## dict["Swell2"] = 5 ## dict["WaveHeight"] = { ## (0,3):1, ## (3,7):2, ## (7,10):3, ## (10,20):5, ## (20,200):10, ## "default":5, ## } ## dict["WindWaveHgt"] = 2 ## return dict def rounding_method_dict(self, tree, node): # Special rounding methods # return { "Wind": self.marineRounding, } 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": 4, "Swell": 1, # ft "Swell2": 1, # ft } 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": 90, # degrees "Swell":60, # degrees "Swell2":60, # degrees } def element_outUnits_dict(self, tree, node): dict = TextRules.TextRules.element_outUnits_dict(self, tree, node) dict["Visibility"] = "NM" return dict 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": 18, # knots or mph depending on product "Period": 5, # seconds "WaveHeight": 2.5, #0, # in feet "WindWaveHgt": 5, # feet } def waveht_scalar_value(self,tree,node,elementName,elementName1): # calculating the scalar value for changes based on wave height wave = tree.stats.get("WaveHeight", node.getTimeRange(), node.getAreaLabel(), mergeMethod="Max") # print wave, "Wave!" if wave is None: return 10 if wave <= 6: rtnval = 6 else: val = wave * .25 rtnval = int(val+0.5) # WxPhrases Overrides def pop_wx_lower_threshold(self, tree, node): # Always report weather return 0 # MarinePhrases Overrides def seasWaveHeight_element(self, tree, node): # Weather element to use for reporting seas # "COMBINED SEAS 10 TO 15 FEET." # IF above wind or swell thresholds return "WaveHeight" def waveHeight_wind_threshold(self, tree, node): # wind value above which waveHeight is reported vs. wind waves # Unit is knots 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) compName = node.getComponentName() if compName == "OFFExtended": return 0 else: return 1 def _skipAreas(self, argDict): # These are edit areas that the formatter will skip return [] 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 waveRange_phrase) # # e.g. # return ["TampaBayWaters"] return ["area3"] 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 list = TextRules.TextRules.wxTypeDescriptors(self) list.append (("*", "R", "*", "*", "showers")) list.append (("*", "S", "*", "*", "showers")) list.append (("*", "VA", "*", "*", "volcanic ash")) #list.append (("*", "T","*", "*", "tstms")) return list 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 list = TextRules.TextRules.wxCoverageDescriptors(self) list.append (("Wide", "*", "*", "*", "scattered")) return list ######################################################################## # ADDED CODE BY MUSONDA TO GIVE WIND DIRECTION RANGE LIKE N TO NE def dirList(self): dirSpan = 22.5 base = 11.25 return[ ('N', 360-base, 361), ('N', 0, base), ('N TO NE', base, base+1*dirSpan), ('NE', base+1*dirSpan, base+2*dirSpan), ('NE TO E', base+2*dirSpan, base+3*dirSpan), ('E', base+3*dirSpan, base+4*dirSpan), ('E TO SE', base+4*dirSpan, base+5*dirSpan), ('SE', base+5*dirSpan, base+6*dirSpan), ('SE TO S', base+6*dirSpan, base+7*dirSpan), ('S', base+7*dirSpan, base+8*dirSpan), ('S TO SW', base+8*dirSpan, base+9*dirSpan), ('SW', base+9*dirSpan, base+10*dirSpan), ('SW TO W', base+10*dirSpan, base+11*dirSpan), ('W', base+11*dirSpan, base+12*dirSpan), ('W TO NW', base+12*dirSpan, base+13*dirSpan), ('NW', base+13*dirSpan, base+14*dirSpan), ('NW TO N', base+14*dirSpan, base+15*dirSpan), ] ## 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": "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": " ", ## "through the day": "", ## "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" , ## } # J.Lewitsky/NHC 04/15/11 uncommented section below to include Local Effects 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 1 ######################################################################## # COMPONENT PRODUCT DEFINITIONS ######################################################################## def _PoP_analysisMethod(self, componentName): # Alternative PoP analysis methods for consistency between PoP and Wx #return self.maxMode #return self.maximum return self.stdDevMaxAvg def addTropical(self, analysisList, phraseList, includeHazards=True): newAnalysisList = [] for entry in analysisList: # Sampling defined as a tuple (field, statistic, temporal rate) # If this is NOT a Wind or WindGust statistic if entry[0] not in ["Hazards", "Wind", "WindGust", "WaveHeight", "Swell"]: # Add this statistic to the new analysisList newAnalysisList.append(entry) newAnalysisList += [ ("Wind", self.vectorModeratedMinMax, [6]), ("WindGust", self.moderatedMinMax, [6]), ("WaveHeight", self.moderatedMax, [6]), ("Swell", self.vectorModeratedMinMax, [6]), ("pws34", self.maximum), ("pws64", self.maximum), ("pwsN34", self.maximum), ("pwsN64", self.maximum), ("pwsD34", self.maximum), ("pwsD64", self.maximum), ] if includeHazards: newAnalysisList.append(("Hazards", self.discreteTimeRangesByKey)) phraseList.insert(0, self.pws_phrase) return newAnalysisList, phraseList def OFFPeriod(self): # type = "component", ## methodList = [ ## self.consolidateSubPhrases, ## self.assemblePhrases, ## self.wordWrap, ## ], analysisList = [ # NOTE: Choose from the following analysis options. # Do not remove the "vectorMinMax" analysis for # "Wind". This is necessary to get an absolute max if # the useWindsForGusts flag is on. # Use the following if you want moderated ranges # (e.g. N WIND 10 to 20 KT) # Set the moderating percentage in the "moderated_dict" # dictionary module. # Set the maximum range values in the "maximum_range_nlValue_dict" # dictionary module. ("Wind", self.vectorModeratedMinMax, [6]), # ("Wind", self.vectorMinMax, [6]), ("WindGust", self.moderatedMax, [3]), ("WaveHeight", self.moderatedMinMax, [6]), # ("WindWaveHgt", self.moderatedMinMax, [6]), ("Swell", self.vectorModeratedMinMax, [6]), ("Swell2", self.vectorModeratedMinMax, [6]), ("Period", self.moderatedMinMax, [6]), ("Period2", self.moderatedMinMax, [6]), ("Wx", self.rankedWx, [12]), # ("T", self.minMax), # ("PoP", self._PoP_analysisMethod("OFFPeriod"), [6]), # ("PoP", self.binnedPercent, [6]), # Use the following if you want moderated # single values (e.g. N WIND 20 KT). # Set the moderating percentage in the "moderated_dict" # dictionary module. # NOTE: If you use these methods, include and uncomment # the "combine_singleValues_flag_dict" in your Local file (see below) #("Wind", self.vectorModeratedMax, [3]), #("Wind", self.vectorMinMax, [12]), #("WindGust", self.moderatedMax, [3]), #("WaveHeight", self.moderatedMax, [6]), #("WindWaveHgt", self.moderatedMax, [6]), #("Swell", self.vectorModeratedMax, [6]), #("Swell2", self.vectorModeratedMax, [6]), #("Period", self.moderatedMax, [6]), #("Period2", self.moderatedMax, [6]), #("Wx", self.rankedWx, [6]), #("T", self.minMax), #("PoP", self._PoP_analysisMethod("OFFPeriod")), #("PoP", self.binnedPercent, [6]), # Use the following if you want absolute ranges. # Set the maximum range values in the "maximum_range_nlValue_dict" # dictionary module. # Split time range in quarters for Wind and WindGust #("Wind", self.vectorMinMax, [3]), #("Wind", self.vectorMinMax, [12]), #("WindGust", self.maximum, [3]), #("WaveHeight", self.minMax, [6]), #("WindWaveHgt", self.minMax, [6]), # Split time range in half for Wx and Swell #("Swell", self.vectorMinMax, [6]), #("Swell2", self.vectorMinMax, [6]), #("Period", self.avg, [6]), #("Period2", self.avg, [6]), #("Wx", self.rankedWx, [6]), #("T", self.minMax), #("PoP", self._PoP_analysisMethod("OFFPeriod")), #("PoP", self.binnedPercent, [6]), ] phraseList = [ # WINDS # (self.marine_wind_withGusts_phrase, self._windLocalEffects_list()), # J.Lewitsky/NHC 04/15/11 uncommented line below to include Local Effects (self.marine_wind_withGusts_phrase,self._windLocalEffects_list()), #CJ try to remove local effects phrase # self.marine_wind_phrase, # Alternative: # (self.marine_wind_phrase, self._windLocalEffects_list()), # self.marine_wind_phrase, #self.gust_phrase, # WAVES ## commented back out for test (commented out below on 08/24/11 - turn back on for swell wording #self.wave_withPeriods_phrase, # Alternative: self.wave_phrase, # SWELLS AND PERIODS #self.swell_withPeriods_phrase, # Alternative: #self.swell_phrase, #self.period_phrase, # WEATHER # self.weather_phrase, # self.weather_phrase, #CJ try to remove local effects phrase #(self.wave_phrase,self._WaveHeightLocalEffects_list), #self.wave_phrase, self.weather_phrase, self.visibility_phrase, ] # CJ Added if self._includeTropical: analysisList, phraseList = self.addTropical(analysisList, phraseList) return { "type": "component", "methodList": [ self.consolidateSubPhrases, self.assemblePhrases, self.wordWrap, ], "analysisList": analysisList, "phraseList": phraseList, "intersectAreas":[ #Areas listed by weather element that will be #intersected with the current area then #sampled and analyzed. #E.g. used in local effects methods. # J.Lewitsky/NHC 04/15/11 added the following for Local Effects from E.Cristensen ("Wind", ["le_gmz013_w_of_90w", "le_gmz013_main", "le_gmz015_s_of_27n", "le_gmz015_main", "le_gmz017_w_of_96w", "le_gmz017_main", "le_gmz019_s_of_24n", "le_gmz019_main", "le_gmz021_straits_of_florida", "le_gmz021_main", "le_gmz023_60nm_of_veracruz", "le_gmz023_main", "le_gmz025_s_of_19n", "le_gmz025_60nm_of_campeche", "le_gmz025_main"]), ("WaveHeight", ["le_gmz013_w_of_90w", "le_gmz013_main", "le_gmz015_s_of_27n", "le_gmz015_main", "le_gmz017_w_of_96w", "le_gmz017_main", "le_gmz019_s_of_24n", "le_gmz019_main", "le_gmz021_straits_of_florida", "le_gmz021_main", "le_gmz023_60nm_of_veracruz", "le_gmz023_main", "le_gmz025_s_of_19n", "le_gmz025_60nm_of_campeche", "le_gmz025_main"]), ] } 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) ## ***commented out entire section below on 08/24/11 for tropical formatter to work*** ## ***need to uncomment for swell wording to work along with self.wave_withPeriods_phrase above! ## ## ## 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] ## # changed to key on swell instead of period ## # changed to use vector instead of scalar in subPhraseSetUp ## # 05/03/11 CNJ/JL ## if periodFlag: ## print "periodFlag is on" ## node.set("periodFlag", 1) ## period = self.ElementInfo("Swell", "Average", primary=0) ## #period = self.ElementInfo("Period", "Average", primary=0) ## elementInfoList.append(period) ## self.subPhraseSetUp(tree, node, elementInfoList, self.vectorConnector) ## #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 ## print elementName ## statDict = node.getStatDict() ## if statDict is None: ## return self.setWords(node,"") ## wave = self.getStats(statDict, elementName) ## if wave is None: ## return self.setWords(node, "") ## min, max = self.getValue(wave, "MinMax") ## threshold = self.nlValue(self.null_nlValue( ## tree, node, elementName, elementName), max) ## if int(min) < threshold and int(max) < threshold: ## return self.setWords(node, "null") ## waveStr = self.getScalarRangeStr(tree, node, elementName, min, max) ## units = self.units_descriptor(tree, node, "units", "ft") ## waveUnit = self.units_descriptor(tree, node, "unit", "ft") ## if int(min) == 1 and int(max) == 1: ## units = waveUnit ## words = waveStr + " " + units ## ## # add swell direction after wave height phrase - for OFFNT products ## # modified from swell_words in MarinePhrases.TextUtility ## # added 05/03/11 (CNJ/JL) ## if "Swell" in statDict.keys(): ## # 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_swell(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 = words + " IN " + 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 = words + " IN " + subPhraseParts[0] ## elif subPhraseParts[1] != "": ## words = words + " IN " + subPhraseParts[1] ## else: ## pass ## #words = "null" ## ## return self.setWords(node, words) ## ## ### Copy of simple_vector_phrase from VectorRelatedPhrases with magnitude values turned off ### Added 05/03/11 (CNJ/JL) ## def simple_vector_phrase_swell(self, tree, node, elementInfo, checkRepeating=1): ## # Create a vector subPhrase ## # Do not repeat mag, dir if same as previous phrase ## elementName = elementInfo.name ## statDict = node.getStatDict() ## stats = self.getStats(statDict, elementName) ## if stats is None: ## return "" ## mag, dir = stats ## minMag, maxMag = self.getValue(mag, "MinMax") ## ## # Save maxMag at component level for other methods to use. ## # THIS IS PARTICULARLY IMPORTANT FOR USE IN THE includeOnlyPhrases_list def ## # below to eliminate certainly wx elements during tropical cyclone ## # situations when certain conditions are met. ## component = node.getComponent() ## maxMagList = component.get("maxMagList") ## if maxMagList is None: ## maxMagList = [maxMag] ## else: ## maxMagList.append(maxMag) ## component.set("maxMagList", maxMagList) ## ## words = self.vector_mag_tafb(tree, node, minMag, maxMag, ## elementInfo.outUnits, elementName) ## #words = self.vector_mag(tree, node, minMag, maxMag, ## # elementInfo.outUnits, elementName) ## if words == "null": ## return words ## magStr = words ## dirStr = self.vector_dir(dir) ## ## if checkRepeating: ## # Set for future reference ## node.set("dirStr", dirStr) ## node.set("magStr", magStr) ## node.set("minMag", minMag) ## node.set("maxMag", maxMag) ## if minMag == 0.0: ## minMag = maxMag ## # Check for repeating mag or dir ## prevNode = node.getPrev() ## if prevNode is not None: ## prevDirStr = prevNode.get("dirStr") ## prevMagStr = prevNode.get("magStr") ## prevMin = prevNode.get("minMag") ## prevMax = prevNode.get("maxMag") ## if prevMin == 0.0: ## prevMin = prevMax ## if prevMin is None or prevMax is None or \ ## prevDirStr is None or prevMagStr is None: ## pass ## elif prevDirStr == dirStr and prevMagStr == magStr: ## pass ## elif prevDirStr == dirStr: ## dirStr = "" ## elif prevMagStr == magStr: ## magStr = "" ## # Prevent "around 10 becoming 5 to 10" ## # "around 10 becoming 10 to 15" ## elif prevMin == prevMax: ## if (minMag == prevMax - 5.0) or (maxMag == prevMax + 5.0): ## magStr = "" ## # Prevent "5 to 10 becoming around 10" ## # "10 to 15 becoming around 10" ## elif minMag == maxMag: ## if (prevMin == maxMag - 5.0) or (prevMax == maxMag + 5.0): ## magStr = "" ## # modified to exclude swell magnitude and include "SWELL" after direction ## words = dirStr + " SWELL" ## #words = dirStr + self.format(magStr) ## return words.lstrip() ## ## def vector_words(self, tree, node): ## # Create a words for a vector element ## elementInfo = node.getAncestor("firstElement") ## ## ###################################################### ## # added section below to set global avgwnd variable ## # this is used by null_nlValue_tafb_dict below ## # 05/04/11 - CNJ/JL ## statDict = node.getStatDict() ## print "statDict in vector_words is:" ## print statDict ## wspd = self.getStats(statDict, "Wind") ## #wspd = self._windDirSpeed(statDict, argDict) ## #wvhgt = self.getStats(statDict, "WaveHeight") ## mag, dir = wspd ## wspd1 = mag[0] ## wspd2 = mag[1] ## print "low wind speed is:" ## print wspd1 ## print "high wind speed is:" ## print wspd2 ## global avgwnd ## avgwnd = (wspd1 + wspd2) / 2 ## print "avg wind speed is:" ## print avgwnd ## # end additional code section ## ###################################################### ## ## if elementInfo is None: ## return self.setWords(node, "") ## words = self.simple_vector_phrase(tree, node, elementInfo) ## if words == "null": ## return self.setWords(node, "null") ## gustPhrase = "" ## if words != "": ## # Add gusts ## gustFlag = node.getAncestor("gustFlag") ## if gustFlag == 1: ## windStats = tree.stats.get("Wind", node.getTimeRange(), node.getAreaLabel(), ## mergeMethod="Max") ## if windStats is not None: ## maxMag, dir = windStats ## statDict = node.getStatDict() ## gustStats = self.getStats(statDict, "WindGust") ## subRange = node.get("timeRange") ## gustPhrase = self.embedded_gust_phrase( ## tree, node, gustStats, maxMag, subRange) ## return self.setWords(node, words + gustPhrase) ## ## def null_nlValue_tafb(self, tree, node, key, value): ## return self.access_dictionary(tree, node, key, value, "null_nlValue_tafb_dict") ## ## def null_nlValue_tafb_dict(self, tree, node): ## # Threshold below which values are considered "null" and not reported. ## # Units depend on the element and product ## dict = TextRules.TextRules.null_nlValue_dict(self, tree, node) ## #timeRange = node.getTimeRange() ## statDict = node.getStatDict() ## #self.__argDict = argDict ## #sampleInfo = [1] ## #self._sampler = self.getSampler("", sampleInfo) ## #analysisList = ["Wind", "WaveHeight"] ## #statDict = self.getStatDict(self._sampler, analysisList, self._timeRangeList, editArea) ## #statList = self.getStatList(self._sampler, self._getAnalysisList_tafb(),\ ## # self._timeRangeList, editArea) ## #print "statList is:" ## #print statList ## print "statDict in null_nlValue is:" ## print statDict ## wspd = self.getStats(statDict, "Wind") ## swl = self.getStats(statDict, "Swell") ## wvhgt = self.getStats(statDict, "WaveHeight") ## #print "statDict after is:" ## print statDict ## #mag, dir = wspd ## swlhgt = swl[0] ## print "avg wind speed in dict is:" ## print avgwnd ## print "swlhgt is:" ## print swlhgt ## ## ###################################################################### ## # FDS wind/swell table ## # Adjust the following table to set minimum swell height required for ## # mention at various wind speeds ## if avgwnd < 5: # 0-4 kt ## if swlhgt < 3: ## dict["Swell"] = 100 ## print "avg wind < 5 & swell < 3, setting dict[swell] = 100" ## else: ## dict["Swell"] = 3 ## print "avg wind < 5 & swell >= 3, setting dict[swell] = 3" ## elif (avgwnd >=5) and (avgwnd < 9): # 5-10 kt ## if swlhgt < 5: ## dict["Swell"] = 100 ## print "avg wind 5-8 & swell < 5, setting dict[swell] = 100" ## else: ## dict["Swell"] = 5 ## print "avg wind 5-8 & swell >= 5, setting dict[swell] = 5" ## elif (avgwnd >=9) and (avgwnd < 12): # near 10 kt ## if swlhgt < 5.5: ## dict["Swell"] = 100 ## print "avg wind 9-11 & swell < 5.5, setting dict[swell] = 100" ## else: ## dict["Swell"] = 5.5 ## print "avg wind 9-11 & swell >= 5.5, setting dict[swell] = 5.5" ## elif (avgwnd >=12) and (avgwnd < 14): # 10-15 kt ## if swlhgt < 6.5: ## dict["Swell"] = 100 ## print "avg wind 12-13 & swell < 6.5, setting dict[swell] = 100" ## else: ## dict["Swell"] = 6.5 ## print "avg wind 12-13 & swell >= 6.5, setting dict[swell] = 6.5" ## elif (avgwnd >=14) and (avgwnd < 17): # near 15 kt ## if swlhgt < 7: ## dict["Swell"] = 100 ## print "avg wind 14-16 & swell < 7, setting dict[swell] = 100" ## else: ## dict["Swell"] = 7 ## print "avg wind 14-16 & swell >= 7, setting dict[swell] = 7" ## elif (avgwnd >= 17) and (avgwnd < 19): # 15-20 kt ## if swlhgt < 8: ## dict["Swell"] = 100 ## print "avg wind < 19 & swell < 8, setting dict[swell] = 100" ## else: ## dict["Swell"] = 8 ## print "avg wind < 19 & swell >= 8, setting dict[swell] = 8" ## elif (avgwnd >= 19) and (avgwnd < 22): # near 20 kt ## if swlhgt < 8: ## dict["Swell"] = 100 ## print "avg wind 19-21 & swell < 8, setting dict[swell] = 100" ## else: ## dict["Swell"] = 8 ## print "avg wind 19-21 & swell >= 8, setting dict[swell] = 8" ## elif (avgwnd >= 22) and (avgwnd < 24): # 20-25 kt ## if swlhgt < 8.5: ## dict["Swell"] = 100 ## print "avg wind 22-23 & swell < 8.5, setting dict[swell] = 100" ## else: ## dict["Swell"] = 8.5 ## print "avg wind 22-23 & swell >= 8.5, setting dict[swell] = 8.5" ## elif (avgwnd >= 24) and (avgwnd < 27): # near 25 kt ## if swlhgt < 8.5: ## dict["Swell"] = 100 ## print "avg wind 24-26 & swell < 8.5, setting dict[swell] = 100" ## else: ## dict["Swell"] = 8.5 ## print "avg wind 24-26 & swell >= 8.5, setting dict[swell] = 8.5" ## elif (avgwnd >= 27) and (avgwnd < 29): # 25-30 kt ## if swlhgt < 9: ## dict["Swell"] = 100 ## print "avg wind 27-28 & swell < 9, setting dict[swell] = 100" ## else: ## dict["Swell"] = 9 ## print "avg wind 27-28 & swell >= 9, setting dict[swell] = 9" ## elif (avgwnd >= 29) and (avgwnd < 32): # near 30 kt ## if swlhgt < 9: ## dict["Swell"] = 100 ## print "avg wind 29-31 & swell < 9, setting dict[swell] = 100" ## else: ## dict["Swell"] = 9 ## print "avg wind 29-31 & swell >= 9, setting dict[swell] = 9" ## elif (avgwnd >= 32) and (avgwnd < 34): # 30-35 kt ## if swlhgt < 10: ## dict["Swell"] = 100 ## print "avg wind 32-33 & swell < 10, setting dict[swell] = 100" ## else: ## dict["Swell"] = 10 ## print "avg wind 32-33 & swell >= 10, setting dict[swell] = 10" ## elif (avgwnd >= 34) and (avgwnd < 37): # near 35 kt ## if swlhgt < 11: ## dict["Swell"] = 100 ## print "avg wind 34-36 & swell < 11, setting dict[swell] = 100" ## else: ## dict["Swell"] = 11 ## print "avg wind 34-36 & swell >= 11, setting dict[swell] = 11" ## elif (avgwnd >= 37) and (avgwnd < 39): # 35-40 kt ## if swlhgt < 12: ## dict["Swell"] = 100 ## print "avg wind 37-38 & swell < 12, setting dict[swell] = 100" ## else: ## dict["Swell"] = 12 ## print "avg wind 37-38 & swell >= 12, setting dict[swell] = 12" ## elif (avgwnd >= 39) and (avgwnd < 42): # near 40 kt ## if swlhgt < 17: ## dict["Swell"] = 100 ## print "avg wind 39-41 & swell < 17, setting dict[swell] = 100" ## else: ## dict["Swell"] = 17 ## print "avg wind 39-41 & swell >= 17, setting dict[swell] = 17" ## else: ## dict["Swell"] = 20 ## # end of FDS wind/swell table ## ######################################################################### ## ## return dict ## ### override to refer to nlValue_tafb ## def vector_mag_tafb(self, tree, node, minMag, maxMag, units, ## elementName="Wind"): ## "Create a phrase for a Range of magnitudes" ## ## # Check for "null" value (below threshold) ## threshold = self.nlValue(self.null_nlValue_tafb( ## tree, node, elementName, elementName), maxMag) ## #threshold = self.nlValue(self.null_nlValue( ## # tree, node, elementName, elementName), maxMag) ## if maxMag < threshold: ## return "null" ## ## # Apply max reported threshold ## maxReportedMag = self.maxReported_threshold(tree, node, elementName, elementName) ## if maxMag >= maxReportedMag: ## maxMag = maxReportedMag ## #minMag = 0 ## ## units = self.units_descriptor(tree, node, "units", units) ## ## if elementName == "Wind": ## if self.marine_wind_flag(tree, node): ## return self.marine_wind_mag(tree, node, minMag, maxMag, units, elementName) ## ## # Check for SingleValue ## if maxMag == minMag: #or minMag == 0: ## around = self.addSpace( ## self.phrase_descriptor(tree, node, "around", elementName)) ## words = around + `int(maxMag)` + " " + units ## else: ## if int(minMag) < threshold: ## upTo = self.addSpace( ## self.phrase_descriptor(tree, node, "up to", elementName)) ## words = upTo + `int(maxMag)` + " " + units ## else: ## valueConnector = self.value_connector(tree, node, elementName, elementName) ## words = `int(minMag)` + valueConnector + `int(maxMag)` + " " + units ## ## # This is an additional hook for customizing the magnitude wording ## words = self.vector_mag_hook(tree, node, minMag, maxMag, units, elementName, words) ## return words ## ## ***commented out entire section above on 08/24/11 for tropical formatter to work*** ## ***need to uncomment for swell wording to work along with self.wave_withPeriods_phrase! def _WaveHeightLocalEffects_list(self, tree, node): leArea1 = self.LocalEffectArea("le_gmz013_main", "") leArea2 = self.LocalEffectArea("le_gmz013_w_of_90w", "W OF 90W") leArea3 = self.LocalEffectArea("le_gmz015_main", "") leArea4 = self.LocalEffectArea("le_gmz015_s_of_27n", "S OF 27N", "") leArea5 = self.LocalEffectArea("le_gmz017_main", "") leArea6 = self.LocalEffectArea("le_gmz017_w_of_96w", "W OF 96W") leArea7 = self.LocalEffectArea("le_gmz019_main", "") leArea8 = self.LocalEffectArea("le_gmz019_s_of_24n", "S OF 24n") leArea9 = self.LocalEffectArea("le_gmz021_main", "") leArea10 = self.LocalEffectArea("le_gmz021_straits_of_florida", "STRAITS OF FLORIDA") leArea11 = self.LocalEffectArea("le_gmz023_main", "") leArea12 = self.LocalEffectArea("le_gmz023_60nm_of_veracruz", "WITHIN 60 NM OF THE COAST OF VERACRUZ") leArea13 = self.LocalEffectArea("le_gmz025_main","") leArea14 = self.LocalEffectArea("le_gmz025_60nm_of_campeche", "WITHIN 60 NM OF COAST OF CAMPECHE") leArea15 = self.LocalEffectArea("le_gmz025_s_of_19n", "S OF 19N") return [self.LocalEffect([leArea1, leArea2], 2, "...EXCEPT "), self.LocalEffect([leArea3, leArea4], 2, "...EXCEPT "), self.LocalEffect([leArea5, leArea6], 2, "...EXCEPT "), self.LocalEffect([leArea7, leArea8], 2, "...EXCEPT "), self.LocalEffect([leArea9, leArea10], 2, "...EXCEPT "), self.LocalEffect([leArea11, leArea12], 2, "...EXCEPT "), self.LocalEffect([leArea13, leArea14, leArea15], 2, "...EXCEPT "), ] def _windLocalEffects_list(self): leArea1 = self.LocalEffectArea("le_gmz013_main", "") leArea2 = self.LocalEffectArea("le_gmz013_w_of_90w", "W OF 90W") leArea3 = self.LocalEffectArea("le_gmz015_main", "") leArea4 = self.LocalEffectArea("le_gmz015_s_of_27n", "S OF 27N", "") leArea5 = self.LocalEffectArea("le_gmz017_main", "") leArea6 = self.LocalEffectArea("le_gmz017_w_of_96w", "W OF 96W") leArea7 = self.LocalEffectArea("le_gmz019_main", "") leArea8 = self.LocalEffectArea("le_gmz019_s_of_24n", "S OF 24n") leArea9 = self.LocalEffectArea("le_gmz021_main", "") leArea10 = self.LocalEffectArea("le_gmz021_straits_of_florida", "STRAITS OF FLORIDA") leArea11 = self.LocalEffectArea("le_gmz023_main", "") leArea12 = self.LocalEffectArea("le_gmz023_60nm_of_veracruz", "WITHIN 60 NM OF THE COAST OF VERACRUZ") leArea13 = self.LocalEffectArea("le_gmz025_main","") leArea14 = self.LocalEffectArea("le_gmz025_60nm_of_campeche", "WITHIN 60 NM OF COAST OF CAMPECHE") leArea15 = self.LocalEffectArea("le_gmz025_s_of_19n", "S OF 19N") return [self.LocalEffect([leArea1, leArea2], 5, "...EXCEPT "), self.LocalEffect([leArea3, leArea4], 5, "...EXCEPT "), self.LocalEffect([leArea5, leArea6], 5, "...EXCEPT "), self.LocalEffect([leArea7, leArea8], 4, "...EXCEPT "), self.LocalEffect([leArea9, leArea10], 5, "...EXCEPT "), self.LocalEffect([leArea11, leArea12], 5, "...EXCEPT "), self.LocalEffect([leArea13, leArea14, leArea15], 5, "...EXCEPT "), ] def OFFExtended(self): return { "type": "component", "methodList": [ self.consolidateSubPhrases, self.assemblePhrases, self.wordWrap, ], "analysisList": [ # NOTE: Choose from the following analysis options. # Do not remove the "vectorMinMax" analysis for # "Wind". This is necessary to get an absolute max if # the useWindsForGusts flag is on. # Use the following if you want moderated ranges # (e.g. N WIND 10 to 20 KT) # Set the moderating percentage in the "moderated_dict" # dictionary module. # Set the maximum range values in the "maximum_range_nlValue_dict" # dictionary module. ("Wind", self.vectorModeratedMinMax, [24]), # ("WindGust", self.moderatedMinMax, [24]), ("WaveHeight", self.moderatedMinMax, [24]), # ("WindWaveHgt", self.moderatedMinMax, [24]), #("Wx", self.rankedWx), #("T", self.minMax), # needed for weather_phrase #("PoP", self._PoP_analysisMethod("OFFExtended")), #("PoP", self.binnedPercent), ("Swell", self.vectorModeratedMinMax, [12]), ("Swell2", self.vectorModeratedMinMax, [12]), # Use the following if you want moderated # single values (e.g. N WIND 20 KT). # Set the moderating percentage in the "moderated_dict" # dictionary module. # NOTE: If you use these methods, include and uncomment # the "combine_singleValues_flag_dict" in your Local file (see below) #("Wind", self.vectorModeratedMax, [6]), #("WindGust", self.moderatedMax, [12]), #("WaveHeight", self.moderatedMax, [12]), #("WindWaveHgt", self.moderatedMax, [12]), #("Wx", self.rankedWx), #("T", self.minMax), #("PoP", self._PoP_analysisMethod("OFFExtended")), #("PoP", self.binnedPercent), #("Swell", self.vectorModeratedMax, [12]), #("Swell2", self.vectorModeratedMax, [12]), # Use the following if you want absolute ranges. # Set the maximum range values in the "maximum_range_nlValue_dict" # dictionary module. # dictionary module. #("Wind", self.vectorMinMax, [6]), #("WindGust", self.minMax, [12]), #("WaveHeight", self.minMax, [12]), #("WindWaveHgt", self.minMax, [12]), #("Wx", self.rankedWx), #("T", self.minMax), #("PoP", self._PoP_analysisMethod("OFFExtended")), #("PoP", self.binnedPercent), #("Swell", self.vectorMinMax, [12]), #("Swell2", self.vectorMinMax, [12]), ], "phraseList":[ # WIND self.marine_wind_phrase, # WAVEHEIGHT # Commented out until fully developed seas wording fixed 9/7/11 CNJ/JL #self.wave_withPeriods_phrase, # Alternative: self.wave_phrase, # SWELLS AND PERIODS #self.swell_withPeriods_phrase, # Alternative: #self.swell_phrase, #self.period_phrase, # WEATHER #self.weather_phrase, #self.visibility_phrase, ], } 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 dict = TextRules.TextRules.combine_singleValues_flag_dict(self, tree, node) #dict["Wind"] = 1 #dict["WindGust"] = 1 #dict["Swell"] = 1 #dict["Swell2"] = 1 #dict["WindWaveHgt"] = 1 #dict["WaveHeight"] = 1 return dict def _getVariables(self, argDict): # Make argDict accessible self.__argDict = argDict # Get Definition variables self._definition = argDict["forecastDef"] for key in self._definition.keys(): exec "self._" + key + "= self._definition[key]" # Get VariableList and _issuance_list variables varDict = argDict["varDict"] for key in varDict.keys(): if type(key) is types.TupleType: label, variable = key exec "self._" + variable + "= varDict[key]" try: self._pdCombo = varDict["Period Combining?"] except: if self._pdCombo == "Yes": self._periodCombining = 1 else: self._periodCombining = 0 # Added CJ # Tropical exceptions try: self._includeTropical = self._includeTropical == "Yes" except: self._includeTropical = False if self._includeTropical: self._periodCombining = 1 # Changed from 0 to 1 by JL (09/04/11) if self._productIssuance == "Morning with Pre-1st Period": self._productIssuance = "Morning" if self._productIssuance == "Afternoon with Pre-1st Period": self._productIssuance = "Afternoon" self._language = argDict["language"] return None def _determineTimeRanges(self, argDict): # Set up the Narrative Definition and initial Time Range self._issuanceInfo = self.getIssuanceInfo( self._productIssuance, self._issuance_list(argDict)) self._timeRange = self._issuanceInfo.timeRange() argDict["productTimeRange"] = self._timeRange self._expireTime = self._issuanceInfo.expireTime() self._issueTime = self._issuanceInfo.issueTime() self._definition["narrativeDef"] = self._issuanceInfo.narrativeDef() if self._periodCombining: self._definition["methodList"] = \ [self.combineComponentStats, self.assembleChildWords] else: self._definition["methodList"] = [self.assembleChildWords] # Calculate current times self._ddhhmmTime = self.getCurrentTime( argDict, "%d%H%M", shiftToLocal=0, stripLeading=0) staticIssueTime=re.sub(r'(\d{3,4} [AP]M).*',r'\1',self._productIssuance) self._timeLabel = staticIssueTime + " " + self.getCurrentTime(argDict, " %Z %a %b %e %Y", stripLeading=1) # self._timeLabel1 = staticIssueTime # HHMM AM # self._timeLabel2 = self.getCurrentTime(argDict, "%Z %a %b %e %Y", stripLeading=1) # EST FRI MMM DD # self._timeLabel = self._timeLabel1 + self._timeLabel2 # Re-calculate issueTime self._issueTime = self.strToGMT(staticIssueTime) expireTimeRange = self.IFP().TimeRange(self._expireTime, self._expireTime + 3600) self._expireTimeStr = self.timeDisplay(expireTimeRange, "", "", "%d%H%M", "") return None def _sampleData(self, argDict): # Sample and analyze the data for the narrative self._narrativeProcessor = ForecastNarrative.ForecastNarrative() error = self._narrativeProcessor.getNarrativeData( argDict, self._definition, self._timeRange, self._areaList, self._issuanceInfo) if error is not None: return error return None def _postProcessProduct(self, fcst, argDict): #fcst = fcst + """NNNN """ # Remember that the product definition calls marine abbreviations """ZFP_ER_Overrides version of AreaFcst._postProcessProduct. Modified to add the capability of retaining forecast text from the previous ZFP. """ self.debug_print("\tZFP_ER_Overrides version of " + "AreaFcst._postProcessProduct") fcst = string.replace(fcst, "%expireTime", self._expireTimeStr) fcst = string.upper(fcst) # Try to preserve text from previous CWF # Get the module first import mergeProds # If this option is desired (i.e. a non zero period was chosen) if type(self._updatePeriodIndex) is type(1) and \ self._updatePeriodIndex >= 0: print "in merge module" # Get previous product prevProd=self.getPreviousProduct(self._prevProdPIL) # If we actually found the previous text if prevProd != "": print "found previous product" # Merge the forecasts fcst=mergeProds.mergeProds()._mergeCWF( fcst, prevProd, self._updatePeriodIndex) ## Corresponding to Forecaster Name at top ## added by J. Lewitsky/NHC 02/04/11 ## found duplicate _postProcessProduct - fixed 05/03/11 CNJ/JL fcst = fcst + "FORECASTER " + self._forecasterName self.setProgressPercentage(100) self.progressMessage(0, 100, self._displayName + " Complete") fcst = re.sub(r' ', " ", fcst) fcst = string.replace(fcst, "NATIONAL WEATHER SERVICE", "NWS") return fcst ######################################################################## # PRODUCT-SPECIFIC METHODS ######################################################################## def _issuance_list(self, argDict): # This method sets up configurable issuance times with associated # narrative definitions. See the Text Product User Guide for documentation. # Added CJ try: includeTropical = self._includeTropical except: includeTropical = False if includeTropical: narrativeDefAM = [ ("OFFPeriod", "period1"), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ] narrativeDefPM = [ ("OFFPeriod", "period1"), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ] else: if self._definition["includeEveningPeriod"] == 1: narrativeDefAM = [ ("OFFPeriod", "period1"), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFExtended", 24), ("OFFExtended", 24) ] narrativeDefPM = [ ("OFFPeriod", "period1"), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFExtended", 24), ("OFFExtended", 24) ] else: narrativeDefAM = [ ("OFFPeriod", "period1"), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFExtended", 24), ("OFFExtended", 24), ("OFFExtended", 24) ] narrativeDefPM = [ ("OFFPeriod", "period1"), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFPeriod", 12), ("OFFExtended", 24), ("OFFExtended", 24), ("OFFExtended", 24) ] return [ ("430 AM", self.DAY(), self.NIGHT(), 16, ".Today...", "early in the morning", "late in the afternoon", 1, narrativeDefAM), ("1030 AM", "issuanceHour", self.NIGHT(), 16, ".THIS AFTERNOON...", "early in the morning", "late in the afternoon", 1, narrativeDefAM), # End times are tomorrow: ("430 PM", self.NIGHT(), 24 + self.DAY(), 24 + 4, ".Tonight...", "late in the night", "early in the evening", 1, narrativeDefPM), ("1030 PM", "issuanceHour", 24 + self.DAY(), 24 + 4, ".Tonight...", "late in the night", "early in the evening", 1, narrativeDefPM), ## ("1230 AM", self.DAY(), self.NIGHT(), 16, ## ".Today...", "early in the morning", "late in the afternoon", ## 1, narrativeDefAM), ## ("400 AM", self.DAY(), self.NIGHT(), 16, ## ".Today...", "early in the morning", "late in the afternoon", ## 1, narrativeDefAM), ## ("930 AM", "issuanceHour", self.NIGHT(), 16, ## ".THIS AFTERNOON...", "early in the morning", "late in the afternoon", ## 1, narrativeDefAM), ## ("1030 AM", "issuanceHour", self.NIGHT(), 16, ## ".THIS AFTERNOON...", "early in the morning", "late in the afternoon", ## 1, narrativeDefAM), ## # End times are tomorrow: ## ("300 PM", self.NIGHT(), 24 + self.DAY(), 24 + 4, ## ".Tonight...", "late in the night", "early in the evening", ## 1, narrativeDefPM), ## ("400 PM", self.NIGHT(), 24 + self.DAY(), 24 + 4, ## ".Tonight...", "late in the night", "early in the evening", ## 1, narrativeDefPM), ## ("930 PM", "issuanceHour", 24 + self.DAY(), 24 + 4, ## ".Overnight...", "late in the night", "early in the evening", ## 1, narrativeDefPM), ## ("1030 PM", "issuanceHour", 24 + self.DAY(), 24 + 4, ## ".Overnight...", "late in the night", "early in the evening", ## 1, narrativeDefPM), ] def lateDay_descriptor(self, statDict, argDict, timeRange): # If time range is in the first period, return period1 descriptor for # late day -- default 3pm-6pm if self._issuanceInfo.period1TimeRange().contains_tr(timeRange): return self._issuanceInfo.period1LateDayPhrase() else: return "LATE" def lateNight_descriptor(self, statDict, argDict, timeRange): # If time range is in the first period, return period1 descriptor for # late night -- default 3am-6am if self._issuanceInfo.period1TimeRange().contains_tr(timeRange): return self._issuanceInfo.period1LateNightPhrase() else: return "LATE" 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 tuples of weather key search tuples in the form: # (cov type inten) # Wildcards are permitted. return [("* *")] # Returns a list of the Hazards allowed for this product in VTEC format. # These are sorted in priority order - most important first. def allowedHazards(self): allActions = ["NEW", "EXA", "EXB", "EXT", "CAN", "CON", "EXP"] tropicalActions = ["NEW", "EXA", "EXB", "EXT", "UPG", "CAN", "CON", "EXP"] marineActions = ["NEW", "EXA", "EXB", "EXT", "CON"] return [ ('HU.W', tropicalActions, 'Tropical'), # HURRICANE WARNING ('TY.W', tropicalActions, 'Tropical'), # TYPHOON WARNING ('TR.W', tropicalActions, 'Tropical'), # TROPICAL STORM WARNING ('HF.W', marineActions, 'Marine'), # HURRICANE FORCE WIND WARNING ('SR.W', marineActions, 'Marine'), # STORM WARNING ('GL.W', marineActions, 'Marine'), # GALE WARNING ('SE.W', marineActions, 'Marine'), # HAZARDOUS SEAS ('UP.W', allActions, 'IceAccr'), # HEAVY FREEZING SPRAY WARNING # added 05/03/11 CNJ/JL to enable expected wording ('GCE', marineActions, 'Marine'), ('SCE', marineActions, 'Marine'), ('HFE', marineActions, 'Marine'), ## ('GL.O', marineActions, 'Local'), ## ('FG.Y', allActions, 'Fog'), # DENSE FOG ADVISORY ## ('SM.Y', allActions, 'Smoke'), # DENSE SMOKE ADVISORY ## ('UP.Y', allActions, 'IceAccr'), # HEAVY FREEZING SPRAY ADVISORY ('AF.Y', allActions, 'Ashfall') # VOLCANIC ASHFALL ADVISORY ] # Returns a list of the Hazards allowed for this product in VTEC format. # These are sorted in priority order - most important first. def allowedHeadlines(self): allActions = ["NEW", "EXA", "EXB", "EXT", "CAN", "CON", "EXP"] tropicalActions = ["NEW", "EXA", "EXB", "EXT", "UPG", "CAN", "CON", "EXP"] marineActions = ["NEW", "EXA", "EXB", "EXT", "CON"] return [ ('HU.W', tropicalActions, 'Tropical'), # HURRICANE WARNING ('TY.W', tropicalActions, 'Tropical'), # TYPHOON WARNING ('TR.W', tropicalActions, 'Tropical'), # TROPICAL STORM WARNING ('HF.W', marineActions, 'Marine'), # HURRICANE FORCE WIND WARNING ('SR.W', marineActions, 'Marine'), # STORM WARNING ('GL.W', marineActions, 'Marine'), # GALE WARNING ('SE.W', marineActions, 'Marine'), # HAZARDOUS SEAS ('UP.W', allActions, 'IceAccr'), # HEAVY FREEZING SPRAY WARNING # added by J. Lewitsky/NHC on 02/12/11 for expected wording ('GALE CONDITIONS EXPECTED', marineActions, 'Marine'), ('STORM CONDITIONS EXPECTED', marineActions, 'Marine'), ('HURRICANE FORCE WINDS EXPECTED', marineActions, 'Marine') ## ('FG.Y', allActions, 'Fog'), # DENSE FOG ADVISORY ## ('SM.Y', allActions, 'Smoke'), # DENSE SMOKE ADVISORY ## ('UP.Y', allActions, 'IceAccr'), # HEAVY FREEZING SPRAY ADVISORY ('AF.Y', allActions, 'Ashfall'), # VOLCANIC ASHFALL ADVISORY ] def headlinesTiming(self, tree, node, key, timeRange, areaLabel, issuanceTime): # Return # "startPhraseType" and "endPhraseType" # Each can be one of these phraseTypes: # "EXPLICIT" will return words such as "5 PM" # "FUZZY4" will return words such as "THIS EVENING" # "DAY_NIGHT_ONLY" use only weekday or weekday "NIGHT" e.g. # "SUNDAY" or "SUNDAY NIGHT" or "TODAY" or "TONIGHT" # Note: You will probably want to set both the # startPhraseType and endPhraseType to DAY_NIGHT_ONLY to # have this work correctly. # "NONE" will result in no words # OR a method which takes arguments: # issueTime, eventTime, timeZone, and timeType # and returns: # phraseType, (hourStr, hourTZstr, description) # You can use "timingWordTableFUZZY8" as an example to # write your own method. # # If you simply return None, no timing words will be used. # Note that you can use the information given to determine which # timing phrases to use. In particular, the "key" is the Hazard # key so different local headlines can use different timing. # startPhraseType = "NONE" endPhraseType = "NONE" #Example code startTime = timeRange.startTime().unixTime() if startTime > issuanceTime + 24 * 3600: # 12 hours past issuance startPhraseType = "DAY_NIGHT_ONLY" endTime = timeRange.endTime().unixTime() if endTime > issuanceTime + 24 * 3600: # 12 hours past issuance endPhraseType = "DAY_NIGHT_ONLY" return startPhraseType, endPhraseType #return None, None def headlines_words(self, tree, node): "Create the phrase for local headlines from the Hazards grids" words = "" areaLabel = tree.getAreaLabel() headlines = tree.stats.get("Hazards", tree.getTimeRange(), areaLabel, mergeMethod = "List") if headlines is None: return self.setWords(node, "") # Sort the headlines by startTime temp = [] for h, tr in headlines: temp.append((tr.startTime(), (h, tr))) temp.sort() newList = [] for t in temp: newList.append(t[1]) headlines = newList # Fetch the set of local headlines allowed for this product allowedHeadlines = [] for key, allActions, cat in self.allowedHeadlines(): allowedHeadlines.append(key) issuanceTime = self._issueTime.unixTime() for key, tr in headlines: # value == list of subkeys if key not in allowedHeadlines: continue timeDescriptor = self.headlinesTimeRange_descriptor( tree, node, key, tr, areaLabel, issuanceTime) headlineWords = AFPS.DiscreteKey_discreteDefinition().keyDesc( "Hazards" + "_SFC", key) # figure out a way to determine if this is beyond 24 hours... and then do a replace on WARNING startTime = tr.startTime().unixTime() if startTime > issuanceTime + 24 * 3600: # 12 hours past issuance headlineWords = string.replace(headlineWords, "WARNING", "FORCE WINDS EXPECTED") if headlineWords == "": # Don't process the "" key continue hookWords = self.hazard_hook(tree, node, key, "", "",tr.startTime(), tr.endTime()) headlineWords = self.convertToLower(headlineWords) headlinePhrase = "..." + headlineWords + timeDescriptor +hookWords + "...\n" words = words + headlinePhrase words = self.convertToUpper(words) return self.setWords(node, words) #########Tropical Overrides##################### def windSpdProb_thresholds(self, tree, node): return [ ((55.0, 80.0), (30.0, 60.0)), # Per 1 (45.0, 25.0), # Per 2 (40.0, 20.0), # Per 3 (35.0, 15.0), # Per 4 (30.0, 10.0), # Per 5 (25.0, 7.0), # Per 6 (20.0, 6.0), # Per 7 (15.0, 5.0), # Per 8 (12.5, 4.0), # Per 9 (10.0, 3.0), # Per 10 ] def getPeriod_5_9_Desc(self, tree, node, maxMag, pws64, pws34): """ Determines contents of PWS phrase for a fifth to ninth period forecast. """ self.debug_print("\tgetPeriod_5_9_Desc") desc = "" self.debug_print("Period time range = %s" % (repr(node.getComponent().getTimeRange())), 1) self.debug_print("PWS34_wrng = %s" % (pws34), 1) self.debug_print("PWS64_wrng = %s" % (pws64), 1) # Grab thresholds for this period component = node.getComponent() windSpdProb_thresholds = self.windSpdProb_thresholds(tree, component) (thresh34, thresh64) = \ windSpdProb_thresholds[node.getComponent().getIndex()] # Display thresholds so we know what we're using self.debug_print("(34 kt threshold, 64 kt threshold) = (%.2f, %.2f)" % (thresh34, thresh64), 1) if (pws64 >= thresh64 or (pws64 + 1.0) >= thresh64) and maxMag >= 15.0: desc = "posHR" elif maxMag >= 64.0: desc = "posHR" elif (pws34 >= thresh34 or (pws34 + 2.5) >= thresh34) and maxMag >= 15.0: desc = "posTS" elif maxMag >= 34.0: desc = "posTS" else: desc = "" return desc def getPeriod_10_14_Desc(self, tree, node, maxMag, pws64, pws34): """ Determines contents of PWS phrase for a fourth period forecast. """ self.debug_print("\tgetPeriod_4_Desc") desc = "" self.debug_print("Period time range = %s" % (repr(node.getComponent().getTimeRange())), 1) self.debug_print("PWS34_wrng = %s" % (pws34), 1) self.debug_print("PWS64_wrng = %s" % (pws64), 1) # Grab thresholds for this period component = node.getComponent() windSpdProb_thresholds = self.windSpdProb_thresholds(tree, component) (thresh34, thresh64) = windSpdProb_thresholds[9] # Display thresholds so we know what we're using self.debug_print("(34 kt threshold, 64 kt threshold) = (%.2f, %.2f)" % (thresh34, thresh64), 1) if (pws64 >= thresh64 or (pws64 + 1.0) >= thresh64) and maxMag >= 15.0: desc = "posHR" elif maxMag >= 64.0: desc = "posHR" elif (pws34 >= thresh34 or (pws34 + 2.5) >= thresh34) and maxMag >= 15.0: desc = "posTS" elif maxMag >= 34.0: desc = "posTS" else: desc = "" return desc ## PERIOD COMBINING def periodCombining_elementList(self, tree, node): # Weather Elements to determine whether to combine periods #return ["Sky", "Wind", "Wx", "PoP", "MaxT", "MinT"] # Marine ############################################################################# # Swell could be added below if necessary to prevent too much period combining # during periods of changing swell - may affect swell wording # CNJ 05/05/11 return ["WaveHeight", "Wind"] ############################################################################# # Diurnal Sky Wx pattern #return ["DiurnalSkyWx"] def periodCombining_startHour(self, tree, node): # Hour after which periods may be combined return 6 ## imported from DiscretePhrases.TextUtility ## added by J.Lewitsky/NHC 03/16/11 from OPC def ctp_DAYNIGHT_DAYNIGHT(self,stext,etext,startPrefix,endPrefix): #return phrases like FROM TONIGHT THROUGH WEDNESDAY hourStr, hourTZstr, s_description = stext[0] #starting text hourStr, hourTZstr, e_description = etext[0] #ending text #special case of description the same if s_description == e_description: return s_description #normal case of different descriptions #### Don't ever want a start prefiix. remove it! s = s_description + ' ' + endPrefix + ' ' +\ e_description return s ## J.Lewitsky/NHC 08/01/11 - 'Tropical Storm Warning' wording was being replaced ## with 'Tropic...Storm Warning' due to string replacement below. Commented out ## for Hurricane Season. Will need to uncomment for extratropical storm warnings. def postProcessPhrase(self, tree, node): words = node.get("words") rval = None if words is not None: words = words.replace("rain showers and thunderstorms", "showers and thunderstorms") # Below replace/re.subs format warning headlines in TAFB style words = words.replace("EXPECTED ...", "EXPECTED...") words = words.replace("WARNING ...", "WARNING...") words = re.sub(r'...GALE WARNING.*', r'...GALE WARNING...', words) #words = re.sub(r'...STORM WARNING.*', r'...STORM WARNING...', words) words = re.sub(r'...HURRICANE FORCE WIND WARNING.*', r'...HURRICANE FORCE WIND WARNING...', words) # Translate phrase # This is necessary so that word-wrap works correctly try: words = self.translateForecast(words, self._language) except: words = self.translateForecast(words, "english") rval = self.setWords(node, words) return rval def actionControlWord(self, hazard, issuanceTime): if not hazard.has_key('act'): print "Error! No field act in hazard record." return "" actionCode = hazard['act'] if actionCode in ["NEW", "EXA", "EXB"]: return "" #return "in effect" elif actionCode == "CON": return "" #return "remains in effect" elif actionCode == "CAN": return "is cancelled" elif actionCode == "EXT": return "" #return "now in effect" elif actionCode == "EXP": deltaTime = issuanceTime - hazard['end'] if deltaTime >= 0: return "has expired" else: return "will expire" elif actionCode == "UPG": return "no longer in effect" else: print actionCode, "not recognized in actionControlWord." return ""