diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/CreateTCVAreaDictionary.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/CreateTCVAreaDictionary.py new file mode 100644 index 0000000000..ac032ed235 --- /dev/null +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/CreateTCVAreaDictionary.py @@ -0,0 +1,288 @@ + +# ---------------------------------------------------------------------------- +# 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. +# +# CreateTCVAreaDictionary +# +# Author: +# ---------------------------------------------------------------------------- + +# The MenuItems list defines the GFE menu item(s) under which the +# Procedure is to appear. +# Possible items are: Populate, Edit, Consistency, Verify, Hazards +MenuItems = ["Populate"] + +try: # See if this is the AWIPS I environment + import AFPS + AWIPS_ENVIRON = "AWIPS1" +except: # Must be the AWIPS II environment + AWIPS_ENVIRON = "AWIPS2" + + +import SmartScript +from LockingFile import File +from com.raytheon.uf.common.localization import PathManagerFactory +from com.raytheon.uf.common.localization import LocalizationContext_LocalizationType as LocalizationType, LocalizationContext_LocalizationLevel as LocalizationLevel +## For documentation on the available commands, +## see the SmartScript Utility, which can be viewed from +## the Edit Actions Dialog Utilities window + +class Procedure (SmartScript.SmartScript): + def __init__(self, dbss): + SmartScript.SmartScript.__init__(self, dbss) + + def execute(self, editArea, timeRange, varDict): + self._siteID = self.getSiteID() + + if AWIPS_ENVIRON == "AWIPS1": + import siteConfig + self._gfeHome = siteConfig.GFESUITE_HOME + self._gfeServer = siteConfig.GFESUITE_SERVER + self._gfePort = siteConfig.GFESUITE_PORT + + self._tcvAreaDictionaryContents = \ +""" +# ---------------------------------------------------------------------------- +# 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. +# +# TCV_AreaDictionary +# TCV_AreaDictionary file +# +# Author: GFE Installation Script +# ---------------------------------------------------------------------------- + +# Here is an example TCVAreaDictionary for just a single zone and with comments +# to talk about the structure of the dictionary. +# +# TCV_AreaDictionary = { +# # Zone +# 'FLZ173': { +# # A list of location names. +# 'locationsAffected': [ +# "Miami Beach", +# "Downtown Miami", +# ], +# +# # Threat statements can be overriden here; anything not overriden here +# # will use the generic threat statements +# 'threatStatements': { +# # Section name: "Wind", "Storm Surge", "Flooding Rain" or "Tornado" +# "Wind": { +# # Threat level: "None", "Low", "Mod", "High" or "Extreme" +# "Extreme": { +# # tr: "nothing to see here", "recovery", "hunker down", +# # "complete preparations" or "check plans" +# "hunker down": { +# # "Make plans: " will be prepended to this +# "planning": "For chance that wind could locally reach major " +\\ +# "hurricane force; enact your emergency action plan " +\\ +# "accordingly", +# # "Take action: " will be prepended to this +# "action": "For extremely dangerous and life threatening wind " +\\ +# "to possibly occur; failure to act may result in " +\\ +# "injury or loss of life", +# # "Prepare: " will be prepended to this +# "preparation": "Aggressively for chance of devastating to " +\\ +# "catastrophic wind impacts based on threat " +\\ +# "assessment that considers plausible worst " +\\ +# "case scenario for safety", +# }, +# }, +# }, +# }, +# +# # Potential impacts statements can be overriden here; anything not +# # overriden here will use the generic potential impacts statements +# 'potentialImpactsStatements': { +# # Section name: "Wind", "Storm Surge", "Flooding Rain" or "Tornado" +# "Wind": { +# # Threat level: "None", "Low", "Mod", "High" or "Extreme" +# "Extreme": [ +# # Each string will be on its own line +# "Widespread power outages with some areas experiencing long-term outages", +# "Many bridges and access routes connecting barrier islands impassable", +# "Structural category to sturdy buildings with some having complete wall and roof failures", +# "Complete destruction of mobile homes", +# "Numerous roads impassable from large debris", +# +# ], +# }, +# }, +# +# # Additional information that will be displayed at the end of the segment +# # The structure is a list containing strings and/or lists. Strings in the +# # same list will be idented the same amount. Introducing a list, idents the +# # text until it ends. For example: +# # +# # 'infoSection': [ +# # "This will be at tab level 0", +# # [ +# # "A new list was introduced so this is at tab level 1", +# # [ +# # "Yet another list so this is tab level 2", +# # "Still at tab level 2 here", +# # ], +# # "We are back at tab level 1 because we ended the list", +# # ], +# # "We ended the other list and are back at tab level 0 now", +# # ] +# 'infoSection': [ +# "LOCAL EVACUATION AND SHELTERING: MIAMI-DADE COUNTY EMERGENCY MANAGEMENT", +# [ +# "HTTP://WWW.MIAMIDADE.GOV/EMERGENCY/", +# ], +# "FAMILY EMERGENCY PLANS: FEDERAL EMERGENCY MANAGEMENT AGENCY", +# [ +# "HTTP://READY.GOV/", +# ], +# "LOCAL WEATHER CONDITIONS AND FORECASTS: NWS MIAMI FLORIDA", +# [ +# "HTTP://WWW.SRH.NOAA.GOV/MFL/", +# ], +# ], +# }, +# } + +TCV_AreaDictionary = { +""" + self._zoneSkeletonContents = { + 'locationsAffected' : [], + 'threatStatements' : {}, + 'potentialImpactsStatements' : {}, + 'infoSection' : [], + } + + TCVAreaDictionary = {} + try: + if AWIPS_ENVIRON == "AWIPS1": + import TCVAreaDictionary + TCVAreaDictionary = TCVAreaDictionary.TCV_AreaDictionary + else: + filename = "gfe/userPython/textUtilities/regular/TCVAreaDictionary.py" + fileContents = self._getFileContents(LocalizationType.CAVE_STATIC, + LocalizationLevel.SITE, + self._siteID, + filename) + + exec(fileContents) + + TCVAreaDictionary = TCV_AreaDictionary + except Exception: + pass + + for zone in self._getZones(): + self._tcvAreaDictionaryContents += " '" + zone + "': {\n" + + # Don't clobber existing dictionary entries + if zone in TCVAreaDictionary: + # Add new entries + for key in self._zoneSkeletonContents: + if key not in TCVAreaDictionary[zone]: + TCVAreaDictionary[zone][key] = self._zoneSkeletonContents[key] + + # Remove entries that are no longer needed + existingKeys = TCVAreaDictionary[zone].keys() + for key in existingKeys: + if key not in self._zoneSkeletonContents: + TCVAreaDictionary[zone].pop(key) + + self._tcvAreaDictionaryContents += self._formatDictionary(TCVAreaDictionary[zone], + tabLevel = 2) + else: + self._tcvAreaDictionaryContents += self._formatDictionary(self._zoneSkeletonContents, + tabLevel = 2) + + self._tcvAreaDictionaryContents += " },\n\n" + + self._tcvAreaDictionaryContents += "}\n" + + with open("/tmp/TCVAreaDictionary.TextUtility", "w") as file: + file.write(self._tcvAreaDictionaryContents) + + self._installDictionary() + + def _installDictionary(self): + from subprocess import call + if AWIPS_ENVIRON == "AWIPS1": + call([self._gfeHome + "/bin/ifpServerText", + "-h", self._gfeServer, + "-p", self._gfePort, + "-s", + "-u", "SITE", + "-n", "TCVAreaDictionary", + "-f", "/tmp/TCVAreaDictionary.TextUtility", + "-c", "TextUtility"]) + else: + call(["/awips2/GFESuite/bin/ifpServerText", + "-o", self._siteID, + "-s", + "-u", "SITE", + "-n", "TCVAreaDictionary", + "-f", "/tmp/TCVAreaDictionary.TextUtility", + "-c", "TextUtility"]) + + def _getZones(self): + editAreasFilename = "gfe/combinations/EditAreas_PublicZones_" + \ + self._siteID + ".py" + zonesKey = "Zones_" + self._siteID + + editAreasFileContents = self._getFileContents(LocalizationType.CAVE_STATIC, + LocalizationLevel.CONFIGURED, + self._siteID, + editAreasFilename) + exec(editAreasFileContents) + + # EASourceMap comes from the EditAreas file + return EASourceMap[zonesKey] + + def _getFileContents(self, loctype, loclevel, locname, filename): + pathManager = PathManagerFactory.getPathManager() + context = pathManager.getContext(loctype, loclevel) + context.setContextName(locname) + localizationFile = pathManager.getLocalizationFile(context, filename) + pythonFile = File(localizationFile.getFile(), filename, 'r') + fileContents = pythonFile.read() + pythonFile.close() + + return fileContents + + def _formatDictionary(self, dictionary, tabLevel, output=""): + TAB = " " * 4 + + for key in dictionary: + output += TAB*tabLevel + repr(key) + ": " + + value = dictionary[key] + if type(value) is dict: + output += "{\n" + output = self._formatDictionary(value, tabLevel+1, output) + output += TAB*tabLevel + "},\n" + elif type(value) is list: + output += "[\n" + output = self._formatList(value, tabLevel+1, output) + output += TAB*tabLevel + "],\n" + else: + output += repr(value) + ",\n" + + return output + + def _formatList(self, theList, tabLevel, output=""): + TAB = " " * 4 + + for value in theList: + if type(value) is dict: + output += TAB*tabLevel + "{\n" + output = self._formatDictionary(value, tabLevel+1, output) + output += TAB*tabLevel + "},\n" + elif type(value) is list: + output += TAB*tabLevel + "[\n" + output = self._formatList(value, tabLevel+1, output) + output += TAB*tabLevel + "],\n" + else: + output += TAB*tabLevel + repr(value) + ",\n" + + return output diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py index 4fa223d733..8d7daf96e9 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py @@ -94,8 +94,8 @@ class HazardsTable(VTECTableUtil.VTECTableUtil): self.__tpcKeys = self.__processJavaCollection(GFEVtecUtil.TROPICAL_PHENSIGS, self.__convertPhensig) self.__tpcBaseETN = '1001' self.__ncKeys = self.__processJavaCollection(GFEVtecUtil.NATIONAL_PHENSIGS, self.__convertPhensig) - self.__ufnKeys = [('HU', 'A'), ('HU', 'S'), ('HU', 'W'), ('TR', 'A'), ('TR', 'W'), - ('TY', 'A'), ('TY', 'W')] + self.__ufnKeys = [('HU', 'A'), ('HU', 'W'), ('TR', 'A'), ('TR', 'W'), + ('TY', 'A'), ('TY', 'W'), ('SS', 'A'), ('SS', 'W')] self.__sitesIgnoreNatlEtn = self.__processJavaCollection(GFEVtecUtil.IGNORE_NATIONAL_ETN, str) diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/SampleAnalysis.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/SampleAnalysis.py index 7e7f6428f8..9defcccf76 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/SampleAnalysis.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/SampleAnalysis.py @@ -1,19 +1,19 @@ ## # 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 +# 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 -# +# 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. ## @@ -1632,6 +1632,133 @@ class SampleAnalysis(CommonUtils.CommonUtils): #print "discreteTimeRangesByKey keyList", keyList return keyList + def mostSignificantDiscreteValue(self, parmHisto, timeRange, componentName, withAux=0): + """Using mostSignificantDiscrete_keyOrder_dict and mostSignificantDiscrete_coveragePercentage_dict, + report the most significant discrete value for the given timeRange. If there is a tie, + report the most significant value. + """ + totalHours = 0 + totalPoints = parmHisto.numberOfGridPoints() + compositeNameUI = parmHisto.parmID().compositeNameUI() + + # Loop over all samples, which are for all grids and + # all keys on each of those grids. + # We will have just one entry per + # discrete key (with or without Aux value). + + #print "\n\nIn mostSignificantDiscreteValue: DataType, TimeRange", "DISCRETE", timeRange + #print "STEP 1 -- Aggregate per grid" + + subkeyTypeDict = {} + # STEP 1: + # For each discrete key found in the grids, + # gather its 'hours' of coverage and 'count' of points covered. + for histSample in parmHisto.histoSamples(): + validTime = TimeRange.TimeRange(histSample.validTime()) + if self.temporalCoverage_flag( + parmHisto, timeRange, componentName, histSample) == 0: + continue + # Get the number of hours inside the timeRange that this + # sample comes from (i.e., we can get a sample that lasts + # for 3 weeks - but only 1 hour of it is inside the + # timeRange - and we only want to rank it by the 1 hour + # inside the range) + # + hours = validTime.intersection(timeRange).duration()/3600 + if hours < 1: + continue + + totalHours += hours + + # Gather the subkey Types for this grid in subkeyTypeDict + # Each entry ends up being a list of tuples: (discreteKey, hours, count) + self.gatherSubkeyTypes( + parmHisto, timeRange, componentName, histSample, 'DISCRETE', hours, + subkeyTypeDict, withAux) + + # STEP 2: For each subkeyType, + # --determine an aggregate subkey and rank i.e. + # aggregate areal coverage over time percentage + # --compare the rank to coverage threshold. + #print "subkeyTypeDict", subkeyTypeDict + keyRankDict = {} # Holds: subkeyType: rank + for subkeyType in subkeyTypeDict.keys(): + #print "\nsubkeyType", subkeyType + subkeyList = subkeyTypeDict[subkeyType] + subkeyTypeRank = 0 + # Determine a subkeyType rank + subkeyTotalPoints = 0 + for subkey, hours, count in subkeyList: + #print " subkey, hours, count", subkey, hours, count + subkeyTotalPoints += count + subkeyTypeRank += hours * count + #print "total points =", subkeyTotalPoints + + #print "subkeyTypeRank =", subkeyTypeRank + #print "totalHours =", totalHours + #print "totalPoints =", totalPoints + # Determine rank for the subkeyType + rank = int(round(float(subkeyTypeRank)/(totalHours*totalPoints)*100.0)) + keyRankDict[subkeyType] = rank + #print "rank =", rank + + # Check to see if each subkeyType meets the required coverage percentage + keyOrderDict = self.mostSignificantDiscrete_keyOrder_dict(parmHisto, timeRange, compositeNameUI) + keyOrder = keyOrderDict[compositeNameUI] + mostSignificantSubkey = None + highestOrderIndex = None + for subkeyType in keyRankDict.keys(): + rank = keyRankDict[subkeyType] + thresholdDict = self.mostSignificantDiscrete_coveragePercentage_dict( + parmHisto, timeRange, componentName, subkeyType) + threshold = thresholdDict.get(compositeNameUI, 0) + #print "threshold =", threshold + flag = rank >= threshold + if not flag: + # Get another chance to pass + flag = self.checkPercentages( + parmHisto, timeRange, componentName, subkeyType, keyRankDict) + if flag: # This type meets the threshold criteria + if self.cleanOutEmptyValues(parmHisto, timeRange, componentName, "DISCRETE"): + # Don't save empty values + #print "Ignoring", subkeyType + continue + try: + orderIndex = keyOrder.index(subkeyType) + if highestOrderIndex is None or orderIndex > highestOrderIndex: + highestOrderIndex = orderIndex + mostSignificantSubkey = subkeyType + #print "Found higher significance key =", subkeyType + except: + pass + else: + #print "didn't make the cut", rank, subkeyType + pass + + #print "mostSignificantSubkey =", mostSignificantSubkey + return mostSignificantSubkey + + def mostSignificantDiscrete_coveragePercentage_dict(self, parmHisto, timeRange, componentName, keyStr): + """ Return the required coverage percentage for the given key which will be + compared to its "rank" i.e. the percentage of areal coverage over the time period. + """ + return { + "WindThreat": 5, + "FloodingRainThreat": 5, + "StormSurgeThreat": 5, + "TornadoThreat": 5, + } + + def mostSignificantDiscrete_keyOrder_dict(self, parmHisto, timeRange, componentName): + """ Returns a list of keys from least to most significant for a discrete type (componentName). """ + threatKeyOrder = [None, "None", "Elevated", "Mod", "High", "Extreme"] + return { + "WindThreat": threatKeyOrder, + "FloodingRainThreat": threatKeyOrder, + "StormSurgeThreat": threatKeyOrder, + "TornadoThreat": threatKeyOrder, + } + ######################################## ## UTILITIES diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TCVDictionary.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TCVDictionary.py new file mode 100644 index 0000000000..815d559d81 --- /dev/null +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TCVDictionary.py @@ -0,0 +1,651 @@ + +# ---------------------------------------------------------------------------- +# 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. +# +# TCV_Dictionary +# TCV_Dictionary file +# +# Author: GFE Installation Script +# ---------------------------------------------------------------------------- + +ThreatStatements = { + "Wind": { + "Extreme": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3, 4, or 5 intensity.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic wind impacts. Life threatening wind possible. Efforts should now be underway to secure all properties.", + "action": "Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3, 4, or 5 intensity.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic wind impacts. Life threatening wind possible. Efforts to secure properties should be completed as soon as practical.", + "action": "Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering. Move to shelter before the wind becomes hazardous.", + }, + "hunker down": { + "planning": "Emergency planning should have included a reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3, 4, or 5 intensity.", + "preparation": "To be safe, preparations should have been made for the potential of devastating to catastrophic wind impacts. Life threatening wind possible. Efforts to secure properties should now be complete.", + "action": "Failure to have adequately sheltered may result in serious injury, loss of life, or immense human suffering. Remain sheltered until the wind subsides. Be ready to shelter within shelter if extreme wind warnings are issued.", + }, + "recovery": { + "planning": "Emergency planning should now focus on response and recovery.", + "preparation": "To be safe, heed the instructions of local officials when moving about. Stay out of restricted areas.", + "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "High": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 or 2 intensity.", + "preparation": "To be safe, aggressively prepare for the potential of extensive wind impacts. Life threatening wind possible. Efforts should now be underway to secure all properties.", + "action": "Failure to adequately shelter may result in serious injury or loss of life.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 or 2 intensity.", + "preparation": "To be safe, aggressively prepare for the potential of extensive wind impacts. Life threatening wind possible. Efforts to secure properties should be completed as soon as practical.", + "action": "Failure to adequately shelter may result in serious injury or loss of life.", + }, + "hunker down": { + "planning": "Emergency planning should have included a reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 or 2 intensity.", + "preparation": "To be safe, preparations should have been made for the potential of extensive wind impacts. Life threatening wind possible. Efforts to secure properties should now be complete.", + "action": "Failure to have adequately sheltered may result in serious injury or loss of life.", + }, + "recovery": { + "planning": "Emergency planning should now focus on response and recovery.", + "preparation": "To be safe, heed the instructions of local officials when moving about. Stay out of restricted areas.", + "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "Mod": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", + "preparation": "To be safe, earnestly prepare for the potential of significant wind impacts. Efforts should now be underway to secure all properties.", + "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", + "preparation": "To be safe, earnestly prepare for the potential of significant wind impacts. Efforts to secure properties should be completed as soon as practical.", + "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life.", + }, + "hunker down": { + "planning": "Emergency planning should have included a reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", + "preparation": "To be safe, preparations should have been made for the potential of significant wind impacts. Efforts to secure properties should now be complete.", + "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life.", + }, + "recovery": { + "planning": "Emergency planning should now focus on response and recovery.", + "preparation": "To be safe, heed the instructions of local officials when moving about. Stay out of restricted areas.", + "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "Elevated": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for tropical storm force wind of 39 to 57 MPH.", + "preparation": "To be safe, prepare for the potential of limited wind impacts. Efforts should now be underway to secure all properties.", + "action": "Failure to adequately shelter may result in serious injury.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for tropical storm force wind of 39 to 57 MPH.", + "preparation": "To be safe, prepare for the potential of limited wind impacts. Efforts to secure properties should be completed as soon as practical.", + "action": "Failure to adequately shelter may result in serious injury.", + }, + "hunker down": { + "planning": "Emergency planning should have included a reasonable threat for tropical storm force wind of 39 to 57 MPH.", + "preparation": "To be safe, preparations should have been made for the potential of limited wind impacts. Efforts to secure properties should now be complete.", + "action": "Failure to adequately shelter may result in serious injury.", + }, + "recovery": { + "planning": "Emergency planning should now focus on response and recovery.", + "preparation": "To be safe, heed the instructions of local officials when moving about. Stay out of restricted areas.", + "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "None": { + "check plans": { + "planning": "Emergency planning for this event need not include a threat for tropical storm force wind. The wind will remain less than 39 MPH, but conditions may still be breezy to windy.", + "preparation": "No immediate preparations needed to guard against tropical wind.", + "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", + }, + "complete preparations": { + "planning": "Emergency planning for this event need not include a threat for tropical storm force wind. The wind will remain less than 39 MPH, but conditions may still be breezy to windy.", + "preparation": "No immediate preparations needed to guard against tropical wind.", + "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", + }, + "hunker down": { + "planning": "Emergency planning for this event need not include a threat for tropical storm force wind. The wind will remain less than 39 MPH, but conditions may still be breezy to windy.", + "preparation": "No immediate preparations needed to guard against tropical wind.", + "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", + }, + "recovery": { + "planning": "Conditions may still be breezy to windy.", + "preparation": "Exercise due safety when moving about.", + "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", + }, + "nothing to see here": { + "planning": "Conditions may still be breeze to windy.", + "preparation": "Exercise due safety when moving about.", + "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", + }, + }, + }, + "Storm Surge": { + "Extreme": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding greater than 9 feet above ground.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic storm surge flooding impacts. Life threatening inundation possible from nearby sea or large inland lake waters. Efforts to evacuate should be underway.", + "action": "Failure to heed evacuation orders may result in serious injury, significant loss of life, or immense human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding greater than 9 feet above ground.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic storm surge flooding impacts. Life threatening inundation possible from nearby sea or large inland lake waters. Efforts to evacuate should be rushed to completion. Evacuations must be complete before driving conditions become unsafe.", + "action": "Failure to heed evacuation orders may result in serious injury, significant loss of life, or immense human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", + }, + "hunker down": { + "planning": "Emergency planning should have included a reasonable threat for peak storm surge flooding greater than 9 feet above ground.", + "preparation": "To be safe, preparations should have been made for the potential of devastating to catastrophic storm surge flooding impacts. Life threatening inundation possible from nearby sea or large inland lake waters. Evacuations should be complete with evacuees relocated to safe shelter.", + "action": "Failure to have heeded evacuation orders may result in serious injury, significant loss of life, or immense human suffering.", + }, + "recovery": { + "planning": "Emergency planning should now focus on response and recovery.", + "preparation": "To be safe, heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters recede and the all-clear is officially given.", + "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "High": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding of 6 to 9 feet above ground.", + "preparation": "To be safe, aggressively prepare for the potential of extensive storm surge flooding impacts. Life threatening inundation possible from nearby sea large inland lake waters. Efforts to evacuate should be underway.", + "action": "Failure to heed evacuation orders may result in serious injury, significant loss of life, or human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding of 6 to 9 feet above ground.", + "preparation": "To be safe, aggressively prepare for the potential of extensive storm surge flooding impacts. Life threatening inundation possible from nearby sea large inland lake waters. Efforts to evacuate should be rushed to completion. Evacuations must be complete before driving conditions become unsafe.", + "action": "Failure to heed evacuation orders may result in serious injury, significant loss of life, or human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", + }, + "hunker down": { + "planning": "Emergency planning should have included a reasonable threat for peak storm surge flooding of 6 to 9 feet above ground.", + "preparation": "To be safe, preparations should have been made for the potential of extensive storm surge flooding impacts. Life threatening inundation possible from nearby sea large inland lake waters. Evacuations should be complete with evacuees relocated to safe shelter.", + "action": "Failure to have heeded evacuation orders may result in serious injury, significant loss of life, or human suffering.", + }, + "recovery": { + "planning": "Emergency planning should now focus on response and recovery.", + "preparation": "To be safe, heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters recede and the all-clear is officially given.", + "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "Mod": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding of 3 to 6 feet above ground.", + "preparation": "To be safe, earnestly prepare for the potential of significant storm surge flooding impacts. Life threatening inundation possible from nearby sea or large inland lake waters. Efforts to evacuate should be underway.", + "action": "Failure to heed evacuation orders or instructions from local officials may result in injury and loss of life. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may needlessly risk lives.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding of 3 to 6 feet above ground.", + "preparation": "To be safe, earnestly prepare for the potential of significant storm surge flooding impacts. Life threatening inundation possible from nearby sea or large inland lake waters. Efforts to evacuate should be rushed to completion. Evacuations must be complete before driving conditions become unsafe.", + "action": "Failure to heed evacuation orders or instructions from local officials may result in injury and loss of life. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may needlessly risk lives.", + }, + "hunker down": { + "planning": "Emergency planning should have included a reasonable threat for peak storm surge flooding of 3 to 6 feet above ground.", + "preparation": "To be safe, preparations should have been made for the potential of significant storm surge flooding impacts. Life threatening inundation possible from nearby sea or large inland lake waters. Evacuations should be complete with evacuees relocated to safe shelter.", + "action": "Failure to have heeded evacuation orders or instructions from local officials may result in injury and loss of life.", + }, + "recovery": { + "planning": "Emergency planning should now focus on response and recovery.", + "preparation": "To be safe, heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters recede and the all-clear is officially given.", + "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "Elevated": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", + "preparation": "To be safe, prepare for the potential of limited storm surge flooding impacts. Efforts should be underway.", + "action": "Follow the instructions of local officials. Consider voluntary evacuation if recommended. Leave if evacuation orders are issued.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", + "preparation": "To be safe, prepare for the potential of limited storm surge flooding impacts. Efforts should be rushed to completion before conditions deteriorate.", + "action": "Follow the instructions of local officials. Consider voluntary evacuation if recommended. Leave if evacuation orders are issued.", + }, + "hunker down": { + "planning": "Emergency planning should have included a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", + "preparation": "To be safe, preparations should have been made for the potential of limited storm surge flooding impacts. Efforts should be complete with evacuees relocated to safe shelter.", + "action": "Continue to follow the instructions of local officials.", + }, + "recovery": { + "planning": "Emergency planning should now focus on response and recovery.", + "preparation": "To be safe, heed the instructions of local officials when moving about. Do not return to flooded areas until the all-clear is officially given.", + "action": "Exercise due safety.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "None": { + "check plans": { + "planning": "Emergency planning for this event need not include a threat for storm surge flooding. Unflooded ground or with spots minimally affected by surge water encroachment. Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "No immediate preparations needed to guard against tropical storm surge flooding.", + "action": "Review your seasonal plan and ensure readiness for the next tropical storm surge event.", + }, + "complete preparations": { + "planning": "Emergency planning for this event need not include a threat for storm surge flooding. Unflooded ground or with spots minimally affected by surge water encroachment. Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "No immediate preparations needed to guard against tropical storm surge flooding.", + "action": "Review your seasonal plan and ensure readiness for the next tropical storm surge event.", + }, + "hunker down": { + "planning": "Emergency planning for this event need not include a threat for storm surge flooding. Unflooded ground or with spots minimally affected by surge water encroachment. Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "No immediate preparations needed to guard against tropical storm surge flooding.", + "action": "Review your seasonal plan and ensure readiness for the next tropical storm surge event.", + }, + "recovery": { + "planning": "Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "Exercise due safety.", + "action": "Review your seasonal plan and ensure readiness for the next tropical storm surge event.", + }, + "nothing to see here": { + "planning": "Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present. ", + "preparation": "Exercise due safety.", + "action": "Review your seasonal plan and ensure readiness for the next tropical storm surge event.", + }, + }, + }, + "Flooding Rain": { + "Extreme": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for historic flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic flooding rain impacts. Life threatening flooding possible from excessive tropical rain.", + "action": "Failure to take action may result in serious injury, significant loss of life, and human suffering. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for historic flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic flooding rain impacts. Life threatening flooding possible from excessive tropical rain.", + "action": "Failure to take action may result in serious injury, significant loss of life, and human suffering. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground.", + }, + "hunker down": { + "planning": "Emergency planning should continue to include a reasonable threat for historic flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, remain prepared for the potential of devastating to catastrophic flooding rain impacts. Life threatening flooding possible from excessive tropical rain.", + "action": "Failure to take action may result in serious injury, significant loss of life, and human suffering. If flash flood watches and warnings are issued, heed recommended actions. Seconds can save lives. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives.", + }, + "recovery": { + "planning": "Emergency planning should continue to include a reasonable threat for historic flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, remain prepared for the potential of devastating to catastrophic flooding rain impacts.", + "action": "Failure to take action may result in serious injury, significant loss of life, and human suffering. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "High": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, aggressively prepare for the potential of extensive flooding rain impacts. Life threatening flooding possible from excessive tropical rain.", + "action": "Failure to take action may result in serious injury or significant loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, aggressively prepare for the potential of extensive flooding rain impacts. Life threatening flooding possible from excessive tropical rain.", + "action": "Failure to take action may result in serious injury or significant loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground.", + }, + "hunker down": { + "planning": "Emergency planning should continue to include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, remain prepared for the potential of extensive flooding rain impacts. Life threatening flooding possible from excessive tropical rain.", + "action": "Failure to take action may result in serious injury or significant loss of life. If flash flood watches and warnings are issued, heed recommended actions. Seconds can save lives. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives.", + }, + "recovery": { + "planning": "Emergency planning should continue to include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, remain prepared for the potential of extensive flooding rain impacts.", + "action": "Failure to take action may result in serious injury or significant loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "Mod": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for moderate flooding where peak rainfall totals notably exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, earnestly prepare for the potential of significant flooding rain impacts.", + "action": "Failure to take action may result in serious injury or loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for moderate flooding where peak rainfall totals notably exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, earnestly prepare for the potential of significant flooding rain impacts.", + "action": "Failure to take action may result in serious injury or loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + }, + "hunker down": { + "planning": "Emergency planning should continue to include a reasonable threat for moderate flooding where peak rainfall totals notably exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, remain prepared for the potential of significant flooding rain impacts.", + "action": "Failure to take action may result in serious injury or loss of life. If flash flood watches and warnings are issued, heed recommended actions. Seconds can save lives. Also listen for possible river flood warnings for longer-term impacts along rivers.", + }, + "recovery": { + "planning": "Emergency planning should continue to include a reasonable threat for moderate flooding where peak rainfall totals notably exceed amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, remain prepared for the potential of significant flooding rain impacts.", + "action": "Failure to take action may result in serious injury or loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "Elevated": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, prepare for the potential of limited flooding rain impacts.", + "action": "Failure to take action may result in serious injury, and in some cases loss of life. If flash flood watches and warnings are issued, heed recommended actions.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, prepare for the potential of limited flooding rain impacts.", + "action": "Failure to take action may result in serious injury, and in some cases loss of life. If flash flood watches and warnings are issued, heed recommended actions.", + }, + "hunker down": { + "planning": "Emergency planning should continue to include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, remain prepared for the potential of limited flooding rain impacts.", + "action": "Failure to take action may result in serious injury, and in some cases loss of life. If flash flood watches and warnings are issued, heed recommended actions.", + }, + "recovery": { + "planning": "Emergency planning should continue to include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for flash flooding and rapid inundation.", + "preparation": "To be safe, remain prepared for the potential of limited flooding rain impacts.", + "action": "Failure to take action may result in serious injury, and in some cases loss of life. If flash flood watches and warnings are issued, heed recommended actions.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "None": { + "check plans": { + "planning": "Emergency planning for this event need not include a threat for rainfall flooding. Heavy rain and nuisance flooding may still occur.", + "preparation": "No immediate preparations needed to guard against excessive tropical rainfall.", + "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + }, + "complete preparations": { + "planning": "Emergency planning for this event need not include a threat for rainfall flooding. Heavy rain and nuisance flooding may still occur.", + "preparation": "No immediate preparations needed to guard against excessive tropical rainfall.", + "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + }, + "hunker down": { + "planning": "Emergency planning for this event need not include a threat for rainfall flooding. Heavy rain and nuisance flooding may still occur.", + "preparation": "No immediate preparations needed to guard against excessive tropical rainfall.", + "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + }, + "recovery": { + "planning": "Heavy rain and nuisance flooding may still occur. ", + "preparation": "Exercise due safety.", + "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + }, + "nothing to see here": { + "planning": "Heavy rain and nuisance flooding may still occur.", + "preparation": "Exercise due safety.", + "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + }, + }, + }, + "Tornado": { + "Extreme": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury, significant loss of life, and human suffering. If tornado watches and warnings are issued, heed recommended actions.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury, significant loss of life, and human suffering. If tornado watches and warnings are issued, heed recommended actions.", + }, + "hunker down": { + "planning": "Emergency planning should continue to include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", + "preparation": "To be safe, remain prepared for the potential of devastating to catastrophic tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury, significant loss of life, and human suffering. If tornado watches and warnings are issued, heed recommended actions. Be ready to quickly move to the safest place within your shelter. Seconds can save lives.", + }, + "recovery": { + "planning": "Emergency planning should continue to include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", + "preparation": "To be safe, remain prepared for the potential of devastating to catastrophic tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury, significant loss of life, and human suffering. If tornado watches and warnings are issued, heed recommended actions.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "High": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for numerous tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths.", + "preparation": "To be safe, aggressively prepare for the potential of extensive tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury or significant loss of life. If tornado watches and warnings are issued, heed recommended actions. Seconds can save lives.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for numerous tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths.", + "preparation": "To be safe, aggressively prepare for the potential of extensive tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury or significant loss of life. If tornado watches and warnings are issued, heed recommended actions.", + }, + "hunker down": { + "planning": "Emergency planning should continue to include a reasonable threat for numerous tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths.", + "preparation": "To be safe, remain prepared for the potential of extensive tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury or significant loss of life. If tornado watches and warnings are issued, heed recommended actions. Be ready to quickly move to the safest place within your shelter. Seconds can save lives.", + }, + "recovery": { + "planning": "Emergency planning should continue to include a reasonable threat for numerous tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths.", + "preparation": "To be safe, remain prepared for the potential of extensive tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury or significant loss of life. If tornado watches and warnings are issued, heed recommended actions.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "Mod": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for scattered tornadoes, with a few possibly strong in intensity.", + "preparation": "To be safe, earnestly prepare for the potential of significant tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury or loss of life. If tornado watches and warnings are issued, heed recommended actions.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for scattered tornadoes, with a few possibly strong in intensity.", + "preparation": "To be safe, earnestly prepare for the potential of significant tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury or loss of life. If tornado watches and warnings are issued, heed recommended actions.", + }, + "hunker down": { + "planning": "Emergency planning should continue to include a reasonable threat for scattered tornadoes, with a few possibly strong in intensity.", + "preparation": "To be safe, remain prepared for the potential of significant tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury or loss of life. If tornado watches and warnings are issued, heed recommended actions. Be ready to quickly move to the safest place within your shelter. Seconds can save lives.", + }, + "recovery": { + "planning": "Emergency planning should continue to include a reasonable threat for scattered tornadoes, with a few possibly strong in intensity.", + "preparation": "To be safe, remain prepared prepare for the potential of significant tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury or loss of life. If tornado watches and warnings are issued, heed recommended actions.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "Elevated": { + "check plans": { + "planning": "Emergency planning should include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", + "preparation": "To be safe, prepare for the potential of limited tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life. If tornado watches and warnings are issued, heed recommended actions.", + }, + "complete preparations": { + "planning": "Emergency planning should include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", + "preparation": "To be safe, prepare for the potential of limited tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life. If tornado watches and warnings are issued, heed recommended actions.", + }, + "hunker down": { + "planning": "Emergency planning should continue to include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", + "preparation": "To be safe, remain prepared for the potential of limited tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life. If tornado watches and warnings are issued, heed recommended actions. Be ready to quickly move to the safest place within your shelter. Seconds can save lives.", + }, + "recovery": { + "planning": "Emergency planning should continue to include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", + "preparation": "To be safe, remain prepare for the potential of limited tornado impacts.", + "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life. If tornado watches and warnings are issued, heed recommended actions.", + }, + "nothing to see here": { + "planning": "", + "preparation": "", + "action": "", + }, + }, + "None": { + "check plans": { + "planning": "Emergency planning for this event need not include a threat for tornadoes. Showers and thunderstorms with strong wind gusts may still occur.", + "preparation": "No immediate preparations needed to guard against tropical tornadoes.", + "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", + }, + "complete preparations": { + "planning": "Emergency planning for this event need not include a threat for tornadoes. Showers and thunderstorms with strong wind gusts may still occur.", + "preparation": "No immediate preparations needed to guard against tropical tornadoes.", + "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", + }, + "hunker down": { + "planning": "Emergency planning for this event need not include a threat for tornadoes. Showers and thunderstorms with strong wind gusts may still occur.", + "preparation": "No immediate preparations needed to guard against tropical tornadoes.", + "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", + }, + "recovery": { + "planning": "Showers and thunderstorms with strong wind gusts may still occur.", + "preparation": "Exercise due safety when moving about.", + "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", + }, + "nothing to see here": { + "planning": "Showers and thunderstorms with strong wind gusts may still occur.", + "preparation": "Exercise due safety when moving about.", + "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", + }, + }, + }, +} + +PotentialImpactStatements = { + "Wind": { + "Extreme": ["Structural damage to sturdy buildings with some experiencing complete roof and wall failures. Complete destruction of mobile homes. Hardest hit locations may be uninhabitable for weeks or months.", + "Numerous large trees snapped or uprooted along with fences and roadway signs blown over. Many roads impassable from large debris.", + "Many bridges, causeways, and access routes connecting barrier islands impassible.", + "Widespread power outages with many areas suffering extended outages."], + "High": ["Considerable damage to roofing and siding materials of sturdy buildings, with some having window, door, and garage door failures leading to structural damage. Mobile homes severely damaged, with some destroyed. Hardest hit locations may be uninhabitable for weeks.", + "Many large trees snapped or uprooted along with fences and roadway signs blown over. Some roads impassable from large debris, and more within heavily wooded places.", + "Several bridges, causeways, and access routes connecting barrier islands impassible.", + "Power outages across large areas, with several places suffering extended outages."], + "Mod": ["Some damage to roofing and siding materials, along with damage to porches, awnings, carports, and sheds. A few buildings experiencing window, door, and garage door failures. Mobile homes damaged, especially if unanchored. Unsecured lightweight objects become dangerous projectiles.", + "Several large trees snapped or uprooted, but with greater numbers in places where trees are shallow rooted or diseased. Several fences and roadway signs blown over. Some roads impassable from large debris, and more within heavily wooded places.", + "A few bridges, causeways, and access routes connecting barrier islands impassible.", + "Scattered power outages, but more prevalent in areas with above ground lines."], + "Elevated": ["Damage to porches, awnings, carports, sheds, and unanchored mobile homes. Unsecured lightweight objects blown about.", + "Many large limbs broken off trees. A few trees snapped or uprooted, but with greater numbers in places where trees are shallow rooted or diseased. Some fences and roadway signs blown over. A few roads impassable from debris, particularly within heavily wooded places.", + "Dangerous driving conditions on bridges and other elevated roadways, especially for high profile vehicles.", + "Scattered power outages, especially in areas with above ground lines."], + "None": ["Little to no potential impacts from wind."], + }, + "Storm Surge": { + "Extreme": ["Widespread deep inundation within coastal flood zones from nearby sea or large inland lake waters, possibly reaching areas well away from the shoreline. Powerful and scouring storm surge flooding greatly accentuated by intense battering wind waves resulting in structural damage to buildings, with many washing away. Damage greatly compounded from considerable floating debris. Hardest hit locations may be uninhabitable for an extended period.", + "Near-shore escape routes and secondary roads washed out or severely flooded. Flood control systems and barriers may become stressed.", + "Extreme beach erosion. New inland cuts possible along with local reshaping of the shoreline.", + "Vast and substantial damage to marinas, docks, and piers. Numerous small craft broken away from moorings with many lifted onshore and stranded."], + "High": ["Large areas of deep inundation within coastal flood zones from nearby sea or large inland lake waters. Powerful and scouring storm surge flooding accentuated by battering wind waves resulting in structural damage to buildings, with several washing away. Damage compounded by floating debris. Hardest hit locations may be uninhabitable for an extended period.", + "Large sections of near-shore escape routes and secondary roads washed out or severely flooded. Flood control systems and barriers may become stressed.", + "Severe beach erosion with significant dune loss.", + "Major damage to marinas, docks, and piers. Many small craft broken away from moorings, especially in unprotected anchorages with some lifted onshore and stranded."], + "Mod": ["Inundation within coastal flood zones of considerable extent from nearby sea or large inland lake waters. Storm surge flooding accentuated by wind waves damaging several buildings, mainly near the coast and in usually vulnerable spots.", + "Sections of near-shore escape routes and secondary roads become weakened or washed out, especially in usually vulnerable low spots.", + "Major beach erosion with heavy surf breaching dunes. Very strong and numerous rip currents.", + "Moderate damage to marinas, docks, and piers. Several small craft broken away from moorings, especially in unprotected anchorages."], + "Elevated": ["Partial inundation within coastal flood zones from nearby sea or large inland lake waters. Deepest flooding mainly along immediate shorelines and in low-lying spots, or in areas farther inland from where higher surge waters move ashore.", + "Sections of near-shore roads and parking lots become overspread with surge water. Driving conditions dangerous in places where surge water covers the road.", + "Moderate beach erosion. Heavy surf also breaching dunes, mainly in usually vulnerable locations. Strong rip currents.", + "Minor to locally moderate damage to marinas, docks, and piers. A few small craft broken away from moorings."], + "None": ["Little to no potential impacts from storm surge flooding."], + }, + "Flooding Rain": { + "Extreme": ["The occurrence of extreme rainfall flooding can immensely hinder emergency preparedness or response actions associated with other tropical event hazards. Attentions and emergency resources may need to be committed to meet an immediate flooding rain threat.", + "Rivers and tributaries may greatly and rapidly overflow their banks in many places with deep moving water. Small streams, creeks, canals, arroyos, and ditches become raging rivers. In mountain areas, deadly runoff may rage down valleys while increasing susceptibility to rockslides and mudslides. Flood control systems and barriers may become stressed.", + "Flood waters can enter numerous structures within multiple communities, some structures becoming uninhabitable or washed away. Numerous places where flood waters cover escape routes. Streets and parking lots become rivers of raging water with underpasses submerged. Driving conditions become life threatening. Numerous road and bridge closures with some weakened or washed out.", + "Flood waters may prompt numerous evacuations and rescues."], + "High": ["The occurrence of major rainfall flooding can greatly hinder emergency preparedness or response actions associated with other tropical event hazards. Attentions and emergency resources may need to be committed to meet an immediate flooding threat.", + "Rivers and tributaries may rapidly overflow their banks in multiple places. Small streams, creeks, canals, arroyos, and ditches become dangerous rivers. In mountain areas, destructive runoff may run quickly down valleys while increasing susceptibility to rockslides and mudslides. Flood control systems and barriers may become stressed.", + "Flood waters can enter many structures within multiple communities, some structures becoming uninhabitable or washed away. Many places where flood waters cover escape routes. Streets and parking lots become rivers of moving water with underpasses submerged. Driving conditions become life threatening. Many road and bridge closures with some weakened or washed out.", + "Flood waters may prompt many evacuations and rescues."], + "Mod": ["The occurrence of moderate rainfall flooding can hinder emergency preparedness or response actions associated with other tropical event hazards. Attentions and emergency resources may need to be partially diverted to meet an immediate flooding threat.", + "Rivers and tributaries may quickly become swollen with swifter currents and overspill their banks in a few places, especially in usually vulnerable spots. Small streams, creeks, canals, arroyos, and ditches overflow.", + "Flood waters can enter some structures or weaken foundations. Several places experience expanded areas of rapid inundation at underpasses, low-lying spots, and poor drainage areas. Some streets and parking lots take on moving water as storm drains and retention ponds overflow. Driving conditions become hazardous along with some road and bridge closures.", + "Flood waters may prompt several evacuations and rescues."], + "Elevated": ["The occurrence of minor rainfall flooding can hinder emergency preparedness or response actions associated with other tropical event hazards.", + "Rivers and tributaries may quickly rise with swifter currents. Small streams, creeks, canals, arroyos, and ditches become swollen and overflow in spots.", + "Flood waters can enter a few structures, especially in usually vulnerable spots. A few places where rapid ponding of water occurs at underpasses, low-lying spots, and poor drainage areas. Several storm drains and retention ponds become near-full and begin to overflow. Some brief road and bridge closures.", + "Rainfall flooding may prompt a few evacuations."], + "None": ["Little to no potential impacts from flooding rain."], + }, + "Tornado": { + "Extreme": ["The occurrence of an outbreak of tornadoes can immensely hinder emergency preparedness or response actions associated with other event tropical hazards. Attentions and emergency resources may need to be temporarily diverted to meet an immediate tornado threat.", + "Many places may experience tornado damage, with several spots of immense destruction, power loss, and communications failures.", + "Hardest hit locations can realize sturdy buildings demolished, structures upon weak foundations swept away, mobile homes obliterated, large trees twisted and snapped with some debarked, vehicles lifted off the ground and thrown with distance, and small boats destroyed. Large and deadly projectiles add considerably to the toll."], + "High": ["The occurrence of numerous tornadoes can greatly hinder emergency preparedness or response actions associated with other tropical event hazards. Attentions and emergency resources may need to be temporarily diverted to meet an immediate tornado threat.", + "Many places may experience tornado damage with a few spots of immense destruction, power loss, and communications failures.", + "Hardest hit locations can realize roof and wall failures of sturdy buildings with some being leveled, structures upon weak foundations blown away, mobile homes obliterated, large trees twisted and snapped with forested trees uprooted, vehicles lifted off the ground and thrown, and small boats destroyed. Large and deadly projectiles add to the toll."], + "Mod": ["The occurrence of scattered tornadoes can hinder emergency preparedness or response actions associated with other tropical event hazards.", + "Several places may experience tornado damage with a few spots of considerable damage, power loss, and communications failures.", + "Hardest hit locations can realize roofs torn off frame houses, mobile homes demolished, boxcars overturned, large trees snapped or uprooted, vehicles lifted off the ground, and small boats tossed about. Dangerous projectiles add to the toll."], + "Elevated": ["The occurrence of isolated tornadoes can hinder emergency preparedness or response actions associated with other tropical event hazards.", + "A few places may experience tornado damage, along with power and communications disruptions.", + "Hardest hit locations can realize roofs peeled off buildings, chimneys toppled, mobile homes pushed off foundations or overturned, large tree tops and branches snapped off, shallow-rooted trees knocked over, moving vehicles blown off roads, and small boats pulled from moorings. Dangerous projectiles of lighter weight."], + "None": ["Little to no potential impacts from tornadoes."], + }, +} + +EvacuationStatements = ["Several parishes and jurisdictions have issued evacuation orders.", + "For those not under evacuation orders, understand that there are inherent risks to evacuation (traffic accidents, congestion, and getting caught on the road during bad weather), so evacuate only if you need to. That would also help keep roadways open for those that are under evacuation orders."] + +OtherPreparednessActions = ["For those under a warning, now is the time to rush to completion preparations for the protection of life and property.", + "People in the warning area should finish preparations now. For those under a watch, review your preparedness plans and be ready to implement them should a warning be issued for your area.", + "For marine interests under a watch or warning, return to port or seek safe harbor. Determine the best strategy for securing your craft.", + "Closely monitor NOAA Weather Radio or other local news outlets for offical storm information. Listen for possible changes to the forecast."] + +AdditionalSources = ["For information on appropriate preparations see ready.gov/louisiana", + "For information on local evacuation shelters see www.emergency.louisana.gov/disaster_evaluation_guide.html", + "For information on creating an emergency plan see getagameplan.org", + "For additional disaster preparedness information see redcross.org"] diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/VTECMessageType.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/VTECMessageType.py index b33c5f6404..5fd81d9741 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/VTECMessageType.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/VTECMessageType.py @@ -39,6 +39,7 @@ VTECMessageTypeDict = { 'CFW' : 'O', 'HLS' : 'O', 'MWW' : 'O', + 'TCV' : 'O', } diff --git a/cave/com.raytheon.viz.gfe/res/spring/gfe.xml b/cave/com.raytheon.viz.gfe/res/spring/gfe.xml index 357da0db2e..17277873c8 100644 --- a/cave/com.raytheon.viz.gfe/res/spring/gfe.xml +++ b/cave/com.raytheon.viz.gfe/res/spring/gfe.xml @@ -24,6 +24,8 @@ HU.W TR.A TR.W + SS.A + SS.W @@ -33,6 +35,8 @@ HU.W TR.A TR.W + SS.A + SS.W diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_HLSTCV_Common.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_HLSTCV_Common.py new file mode 100644 index 0000000000..96769179f4 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_HLSTCV_Common.py @@ -0,0 +1,1978 @@ +import GenericHazards +import string, time, os, re, types, copy, LogStream, collections +import ModuleAccessor, SampleAnalysis, EditAreaUtils +import math + + +try: # See if this is the AWIPS I environment + import AFPS + from AFPS import AbsTime + from IFPDialog import Dialog + AWIPS_ENVIRON = "AWIPS1" +except: # Must be the AWIPS II environment + from AbsTime import * + from StartupDialog import IFPDialog as Dialog + from LockingFile import File + from com.raytheon.uf.common.localization import PathManagerFactory + from com.raytheon.uf.common.localization import LocalizationContext_LocalizationType as LocalizationType + AWIPS_ENVIRON = "AWIPS2" + +class TextProduct(GenericHazards.TextProduct): + Definition = copy.deepcopy(GenericHazards.TextProduct.Definition) + + def __init__(self): + GenericHazards.TextProduct.__init__(self) + + ###################################################### + # Populate Product Parts for HLS and TCV + ###################################################### + + ################# Product Level + + def _wmoHeader(self, productDict, productSegmentGroup, arguments=None): + headerDict = collections.OrderedDict() + headerDict['TTAAii'] = self._wmoID + headerDict['originatingOffice'] = self._backupFullStationID # Will be siteID if not in backup mode + headerDict['productID'] = self._productID + headerDict['siteID'] = self._site + headerDict['fullStationID'] = self._fullStationID + headerDict['ddhhmmTime'] = self._ddhhmmTime + productDict['wmoHeader'] = headerDict + + def _productHeader(self, productDict, productSegmentGroup, arguments=None): + headerDict = dict() + headerDict['disclaimer'] = 'This XML wrapped text product should be considered COMPLETELY EXPERIMENTAL. The National Weather Service currently makes NO GUARANTEE WHATSOEVER that this product will continue to be supplied without interruption. The format of this product MAY CHANGE AT ANY TIME without notice.' + headerDict['cityState'] = self._wfoCityState + headerDict['stormNumber'] = self._getStormNumberStringFromTCP() + self._productName = self.checkTestMode( + self._argDict, productSegmentGroup.get('productName') + self._areaName) + headerDict['productName'] = self._productName + headerDict['stormType'] = self._getStormTypeFromTCP() + headerDict['stormName'] = self._getStormNameFromTCP() + headerDict['advisoryType'] = self._getAdvisoryTypeFromTCP() + headerDict['advisoryNumber'] = self._getAdvisoryNumberStringFromTCP() + headerDict['issuedByString'] = self.getIssuedByString() + headerDict['issuanceTimeDate'] = self._timeLabel + productDict['productHeader'] = headerDict + + ############################################################### + ### Hazards related methods + + def _initializeHazardsTable(self, argDict): + import VTECMessageType + productID = self._pil[0:3] + vtecMode = VTECMessageType.getVTECMessageType(productID.upper()) + argDict["vtecMode"] = vtecMode + + self._setVTECActiveTable(argDict) + + # Need to check hazards against all edit areas in the CWA MAOR + argDict["combinations"]= [(self._allAreas(),"Region1")] + + self._hazardsTable = self._getHazardsTable(argDict, self.filterMethod) + argDict["hazards"] = self._hazardsTable + + def _setVTECActiveTable(self, argDict): + dataMgr = argDict["dataMgr"] + gfeMode = dataMgr.getOpMode().name() + + if gfeMode == "PRACTICE": + argDict["vtecActiveTable"] = "PRACTICE" + else: + argDict["vtecActiveTable"] = "active" + + def _getAllVTECRecords(self): + allRecords = [] + for segment in self._segmentList: + vtecRecords = self._hazardsTable.getHazardList(segment) + allRecords += vtecRecords + + return allRecords + + def _ignoreActions(self): + # Ignore hazards with these action codes in the overview headlines + # NOTE: the VTEC and segments will still include them correctly. + return ['CAN', 'UPG'] + + # In order to have the HazardsTable use the allowedHeadlines list, + # we need to supply a filterMethod that uses allowedHeadlines instead of allowedHazards + def _getAllowedHazardList(self, allowedHazardList=None): + if allowedHazardList is None: + allowedHazardList = self.allowedHazards() + hazardList = [] + for h in allowedHazardList: + if type(h) is types.TupleType: + hazardList.append(h[0]) + else: + hazardList.append(h) + return hazardList + + def _altFilterMethod(self, hazardTable, allowedHazardsOnly=False): + # Remove hazards not in allowedHeadlines list + allowedHazardList = self._getAllowedHazardList(self.allowedHeadlines()) + return self._filterHazards(hazardTable, allowedHazardList, + allowedHazardsOnly) + + def _filterHazards(self, hazardTable, allowedHazardList, + allowedHazardsOnly=False): + newTable = [] + hazStr = "" + for i in range(len(hazardTable)): + if hazardTable[i]['sig'] != "": # VTEC + hazStr = hazardTable[i]['phen'] + "." + hazardTable[i]['sig'] + else: #non-VTEC + hazStr = hazardTable[i]['phen'] + + if hazStr in allowedHazardList: + newTable.append(hazardTable[i]) + if allowedHazardsOnly: + return newTable + # get a raw list of unique edit areas + zoneList = [] + for t in newTable: + if t['id'] not in zoneList: + zoneList.append(t['id']) + for zone in zoneList: + # Remove lower priority hazards of the same type + self.filterZoneHazards(zone, newTable) + return newTable + + def _getAdditionalHazards(self): + argDict = self._argDict + argDict['definition'] = self._definition + altHazards = self._getHazardsTable(argDict, self._altFilterMethod) + conTable = altHazards.consolidatedTableByID() + + # Consolidate across action codes + hazDict = {} + for hazard in conTable: + hdln=hazard['hdln'] + phen=hazard['phen'] + sig=hazard['sig'] + act=hazard['act'] + if act in self._ignoreActions(): + continue + for area in hazard['id']: + hazDict.setdefault((hdln, phen, sig), []).append(area) + + #print "hazDict", hazDict + hazardHdlns=[] + huAreas = [] +# print "\nAdditional Hazard Headlines" + for key in hazDict.keys(): + hdln, phen, sig = key + huAreas = huAreas + hazDict[key] + hazardHdln = ((hdln, "NEW", phen,sig), hazDict[key], [],[],[]) + #print " ", hazardHdln, hazDict[key] + hazardHdlns.append(hazardHdln) + return hazardHdlns, huAreas + + def _checkHazard(self, hazardHdlns, phenSigList, checkAreaTypes=None, + checkAreas=None, returnList=False, mode="any", includeCAN=False): + # Given a list of hazards in the form + # (key, landList, marineList, coastalList, inlandList) + # where key is (hdln, act, phen, sig) and the lists show which areas + # contain the hazard + # If mode == "any": + # Check to see if any of the given phenSigList = [(phen, sig), (phen, sig)] + # are found + # If mode == "all": + # Check to see if all of the given phenSigList are found + # IF checkAreaTypes is given, then check against that particular area type(s) i.e. + # "land", "marine", etc. + # IF checkAreas is given, only return areas that are in that list + # IF returnList=True, returns a list of (key, areas) that meet the criteria + # IF includeCAN is True then CAN hazards will be included as well. + # Otherwise, they are ignored. + # + # E.g. hdlnList = self._checkHazard(hazardHdlns, [("FA","W")], returnList=True) + print "_checkHazard hazardHdlns is ", hazardHdlns + print "_checkHazard phenSigList is ", phenSigList + chosen = [] + for key, landList, marineList, coastalList, inlandList in hazardHdlns: +# print "what is mode?", mode + + # SARAH - we do not want to consider marine hazards in this product +# hazAreas = landList+marineList + hazAreas = landList + hazValue = (key, hazAreas) + print "hazValue is ", hazValue + hdln, act, phen, sig = key + if not includeCAN and act == "CAN": + continue + for checkPhen, checkSig in phenSigList: + print "checkPhen is ", checkPhen + print "checkSig is ", checkSig + if phen == checkPhen and sig == checkSig: + if checkAreaTypes is not None: + # Check for land, marine, etc. + for checkAreaType in checkAreaTypes: + exec "testList = " + checkAreaType + "List" +# print "testList is", testList + if testList != []: + chosen.append(hazValue) +# print "chosen is ", chosen + elif checkAreas is not None: + acceptedAreas=[] + for hazArea in hazAreas: + if hazArea in checkAreas: + acceptedAreas.append(hazArea) + if acceptedAreas!=[]: + chosen.append((key, acceptedAreas)) + else: + chosen.append(hazValue) + if not returnList and chosen!=[]: break + + print "MATT _checkHazard chosen = %s" % (repr(chosen)) + if not returnList: + return chosen!=[] + return chosen + + def getVtecRecords(self, segment, vtecEngine=None): + vtecRecords = self._hazardsTable.getHazardList(segment) + return vtecRecords + + def _getHazardsTable(self, argDict, filterMethod, editAreas=None): + # Set up edit areas as list of lists + # Need to check hazards against all edit areas in the CWA MAOR + argDict["combinations"]= [(self._allAreas(),"Region1")] + dfEditAreas = argDict["combinations"] + editAreas = [] + for area, label in dfEditAreas: + if type(area) is types.ListType: + editAreas.append(area) + elif type(area) is types.TupleType: #LatLon + editAreas.append([self.__getLatLonAreaName(area)]) + else: + editAreas.append([area]) + # Get Product ID and other info for HazardsTable + pil = self._pil.upper() # Ensure PIL is in UPPERCASE + stationID4 = self._fullStationID + productCategory = pil[0:3] #part of the pil + definition = argDict['definition'] + sampleThreshold = definition.get("hazardSamplingThreshold", (10, None)) + # Process the hazards + accurateCities = definition.get('accurateCities', 0) + cityRefData = [] + import HazardsTable + hazards = HazardsTable.HazardsTable( + argDict["ifpClient"], editAreas, productCategory, filterMethod, + argDict["databaseID"], + stationID4, argDict["vtecActiveTable"], argDict["vtecMode"], sampleThreshold, + creationTime=argDict["creationTime"], accurateCities=accurateCities, + cityEditAreas=cityRefData, dataMgr=argDict['dataMgr']) + return hazards + + ############################################################### + ### Time related methods + + def _initializeTimeVariables(self, argDict): + argDict['creationTime'] = int(time.time()/60)*60.0 + self._issueTime_secs = argDict['creationTime'] + self._issueTime = self._issueTime_secs * 1000 # in milliseconds + + self._ddhhmmTime = self.getCurrentTime( + argDict, "%d%H%M", shiftToLocal=0, stripLeading=0) + self._currentTime = self._issueTime_secs + self._expireTime = self._issueTime_secs + self._purgeTime*3600 + self._timeLabel = self.getCurrentTime( + argDict, "%l%M %p %Z %a %b %e %Y", stripLeading=1) + + def _determineTimeRanges(self, argDict): + # Set up the time range for 0-120 hours + + # Create a time range from the issuanceHour out 120 hours + startTime = self._calculateStartTime(time.localtime(self._issueTime_secs)) + self._timeRange = self.makeTimeRange(startTime, startTime+120*3600) + + # Determine the time range list, making sure they are on hour boundaries + # w.r.t. midnight today according to the resolution + subRanges = self.divideRange(self._timeRange, self._resolution()) + trList = [] + for tr in subRanges: + # print tr + trList.append((tr, "Label")) + self._timeRangeList = trList + + def _calculateStartTime(self, localCreationTime): + year = localCreationTime[0] + month = localCreationTime[1] + day = localCreationTime[2] + hour = localCreationTime[3] + + # Now "truncate" to a 6-hourly boundary and compute startTime in local Time. + hour = int (int(hour/6) * 6) + startTime = absTimeYMD(year, month, day, hour) + # Finally, convert back to GMT + localTime, shift = self.determineTimeShift() + startTime = startTime - shift + + return startTime + + def _resolution(self): + return 3 + + ############################################################### + ### Sampling and Statistics related methods + + def _getStatValue(self, statDict, element, method=None, dataType=None): + stats = statDict.get(element, None) + if stats is None: return None + if type(stats) is types.ListType: + stats = stats[0] + stats, tr = stats + if dataType==self.VECTOR(): + stats, dir = stats + return self.getValue(stats, method) + + ############################################################### + ### Area, Zone and Segment related methods + + def _allAreas(self): + return self._inlandAreas() + self._coastalAreas() + + def _computeIntersectAreas(self, editAreas, argDict): + editAreaUtils = EditAreaUtils.EditAreaUtils() + editAreaUtils.setUp(None, argDict) + surgeEditArea = editAreaUtils.getEditArea("StormSurgeWW_EditArea", argDict) + intersectAreas =[] + for (_, editAreaLabel) in editAreas: + editArea = editAreaUtils.getEditArea(editAreaLabel, argDict) + intersectAreaLabel = "intersect_"+editAreaLabel + intersectArea = editAreaUtils.intersectAreas(intersectAreaLabel, editArea, surgeEditArea) + grid = intersectArea.getGrid() + if grid.isAnyBitsSet(): + editAreaUtils.saveEditAreas([intersectArea]) + intersectAreas.append((intersectAreaLabel, intersectAreaLabel)) + + return intersectAreas + + ############################################################### + ### Storm Information and TCP related methods + + def _initializeStormInformation(self): + self._stormType = None + self._stormName = None + self._advisoryType = None + self._advisoryNumber = None + self._stormNumber = None + + if self._useTestTCP(): + self._TCP = self._testTCP() + elif "Enter PIL below" in self._StormInfo: + if len(self._StormInfo_entry.strip()) == 0: + return "You need to enter the PIL" + else: + # Ensure PIL is in UPPERCASE + self._TCP = self.getPreviousProduct(self._StormInfo_entry.strip().upper()) + else: + self._TCP = self.getPreviousProduct(self._StormInfo) + + self._parseTCP(self._TCP) + + return None + + def _parseTCP(self, tcp): + # This pattern will handle multiple word names + # (including certain special characters). + # This is for the NHC format. + mndSearch = re.search("(?im)^.*?(?PHURRICANE|(SUB|POST.?)?TROPICAL " + + "(STORM|DEPRESSION|CYCLONE)|(SUPER )?TYPHOON|" + + "REMNANTS OF) (?P[A-Z0-9\-\(\) ]+?)" + + "(?PSPECIAL |INTERMEDIATE )?ADVISORY " + + "NUMBER[ ]+(?P[A-Z0-9]+)[ ]*", tcp) + + if mndSearch is not None: + self._stormType = mndSearch.group("stormType").strip() + self._stormName = mndSearch.group("stormName").strip() + advisoryType = mndSearch.group("advisoryType") + if advisoryType is not None: + self._advisoryType = advisoryType.strip() + self._advisoryNumber = mndSearch.group("advisoryNumber").strip() + + senderSearch = re.search("(?im)^(?P(NWS (TPC/)?NATIONAL HURRICANE CENTER|" + + "NATIONAL WEATHER SERVICE).*?)$", tcp) + + if senderSearch is not None: + sender = senderSearch.group("sender") + senderParts = sender.split(" ") + # If the storm number is mentioned, it will be the last "word" of the line + stormNumber = senderParts[-1] + if len(stormNumber) == 8 and \ + stormNumber[0:2].isalpha() and \ + stormNumber[2:].isdigit(): + self._stormNumber = stormNumber.strip() + + def _getStormTypeFromTCP(self): + return self._stormType + + def _getStormNameFromTCP(self): + return self._stormName + + def _getAdvisoryTypeFromTCP(self): + return self._advisoryType + + def _getAdvisoryNumberStringFromTCP(self): + return self._advisoryNumber + + def _getStormNumberStringFromTCP(self): + if self._stormNumber is not None: + return self._stormNumber + else: + return "" + + ## Used for testing and debugging + def _useTestTCP(self): + #return True + return False + + def _testTCP(self): + return \ +"""337 +WTNT34 KNHC 250256 +TCPAT4 + +BULLETIN +TROPICAL STORM ISAAC ADVISORY NUMBER 16 +NWS NATIONAL HURRICANE CENTER MIAMI FL AL092012 +1100 PM EDT FRI AUG 24 2012 + +...ISAAC GETTING BETTER ORGANIZED AS IT MOVES NORTHWESTWARD TOWARD +HAITI... + + +SUMMARY OF 1100 PM EDT...0300 UTC...INFORMATION +----------------------------------------------- +LOCATION...17.7N 72.5W +ABOUT 65 MI...100 KM SSW OF PORT AU PRINCE HAITI +ABOUT 245 MI...395 KM SE OF GUANTANAMO CUBA +MAXIMUM SUSTAINED WINDS...70 MPH...110 KM/H +PRESENT MOVEMENT...NW OR 310 DEGREES AT 14 MPH...22 KM/H +MINIMUM CENTRAL PRESSURE...990 MB...29.23 INCHES + + +WATCHES AND WARNINGS +-------------------- +CHANGES WITH THIS ADVISORY... + +A HURRICANE WATCH AND A TROPICAL STORM WARNING HAVE BEEN ISSUED FOR +ALL OF THE FLORIDA KEYS...INCLUDING FLORIDA BAY...AND FOR THE COAST +OF THE SOUTHERN FLORIDA PENINSULA FROM OCEAN REEF ON THE EAST COAST +WESTWARD TO BONITA BEACH ON THE WEST COAST. + +A TROPICAL STORM WARNING HAS BEEN ISSUED FOR THE SOUTHEAST FLORIDA +COAST FROM NORTH OF OCEAN REEF NORTHWARD TO JUPITER INLET...AND FOR +LAKE OKEECHOBEE. + +THE GOVERNMENT OF THE BAHAMAS HAS ISSUED A TROPICAL STORM WARNING +FOR ALL OF THE NORTHWESTERN BAHAMAS. + +A TROPICAL STORM WATCH HAS BEEN ISSUED FOR THE EAST-CENTRAL FLORIDA +COAST FROM NORTH OF JUPITER INLET TO SEBASTIAN INLET. + +THE CAYMAN ISLANDS METEOROLOGICAL SERVICE HAS ISSUED A TROPICAL +STORM WATCH FOR THE CAYMAN ISLANDS. + +SUMMARY OF WATCHES AND WARNINGS IN EFFECT... + +A HURRICANE WATCH IS IN EFFECT FOR... +* HAITI +* FLORIDA KEYS INCLUDING THE DRY TORTUGAS +* FLORIDA BAY +* THE FLORIDA EAST COAST FROM OCEAN REEF SOUTHWARD +* THE FLORIDA WEST COAST FROM BONITA BEACH SOUTHWARD + +A TROPICAL STORM WARNING IS IN EFFECT FOR... +* DOMINICAN REPUBLIC +* HAITI +* CUBAN PROVINCES OF CIEGO DE AVILA...SANCTI SPIRITUS...VILLA +CLARA...CAMAGUEY...LAS TUNAS...GRANMA...HOLGUIN...SANTIAGO DE +CUBA...AND GUANTANAMO +* THE BAHAMAS +* TURKS AND CAICOS ISLANDS +* THE FLORIDA KEYS INCLUDING THE DRY TORTUGAS +* THE FLORIDA EAST COAST FROM JUPITER INLET SOUTHWARD +* THE FLORIDA WEST COAST FROM BONITA BEACH SOUTHWARD +* FLORIDA BAY AND LAKE OKEECHOBEE + +A TROPICAL STORM WATCH IS IN EFFECT FOR... +* CUBAN PROVINCES OF MATANZAS AND CIENFUEGOS +* JAMAICA +* THE FLORIDA EAST COAST NORTH OF JUPITER INLET TO SEBASTIAN INLET + +A HURRICANE WATCH MEANS THAT HURRICANE CONDITIONS ARE POSSIBLE +WITHIN THE WATCH AREA...IN THIS CASE WITHIN THE NEXT 24 TO 36 HOURS. + +A TROPICAL STORM WARNING MEANS THAT TROPICAL STORM CONDITIONS ARE +EXPECTED SOMEWHERE WITHIN THE WARNING AREA WITHIN 36 HOURS. + +A TROPICAL STORM WATCH MEANS THAT TROPICAL STORM CONDITIONS ARE +POSSIBLE WITHIN THE WATCH AREA...GENERALLY WITHIN 48 HOURS. + +INTERESTS IN THE REMAINDER OF CUBA AND THE REMAINDER OF THE FLORIDA +PENINSULA SHOULD MONITOR THE PROGRESS OF ISAAC. + +FOR STORM INFORMATION SPECIFIC TO YOUR AREA IN THE UNITED +STATES...INCLUDING POSSIBLE INLAND WATCHES AND WARNINGS...PLEASE +MONITOR PRODUCTS ISSUED BY YOUR LOCAL NATIONAL WEATHER SERVICE +FORECAST OFFICE. FOR STORM INFORMATION SPECIFIC TO YOUR AREA OUTSIDE +THE UNITED STATES...PLEASE MONITOR PRODUCTS ISSUED BY YOUR NATIONAL +METEOROLOGICAL SERVICE. + + +DISCUSSION AND 48-HOUR OUTLOOK +------------------------------ +AT 1100 PM EDT...0300 UTC...THE CENTER OF TROPICAL STORM ISAAC WAS +LOCATED NEAR LATITUDE 17.7 NORTH...LONGITUDE 72.5 WEST. ISAAC IS +MOVING TOWARD THE NORTHWEST NEAR 14 MPH...22 KM/H...BUT IS EXPECTED +TO RESUME A FASTER FORWARD SPEED TOWARD THE NORTHWEST TONIGHT +THROUGH SUNDAY. ON THE FORECAST TRACK...THE CENTER OF ISAAC SHOULD +MAKE LANDFALL IN HAITI TONIGHT...MOVE NEAR OR OVER SOUTHEASTERN +CUBA ON SATURDAY...MOVE NEAR OR OVER CENTRAL CUBA SATURDAY NIGHT... +AND APPROACH THE FLORIDA KEYS ON SUNDAY. + +MAXIMUM SUSTAINED WINDS ARE NEAR 70 MPH...110 KM/H...WITH HIGHER +GUSTS. LITTLE CHANGE IN STRENGTH IS LIKELY BEFORE LANDFALL... +FOLLOWED BY SOME WEAKENING AS THE CENTER CROSSES HAITI AND +SOUTHEASTERN CUBA. + +TROPICAL-STORM-FORCE WINDS EXTEND OUTWARD UP TO 230 MILES... +370 KM...MAINLY NORTHWEST AND NORTHEAST OF THE CENTER. + +ESTIMATED MINIMUM CENTRAL PRESSURE IS 990 MB...29.23 INCHES. + + +HAZARDS AFFECTING LAND +---------------------- +RAINFALL...TOTAL RAINFALL ACCUMULATIONS OF 8 TO 12 INCHES...WITH +MAXIMUM AMOUNTS OF 20 INCHES...ARE POSSIBLE OVER HISPANIOLA. THESE +RAINS COULD CAUSE LIFE-THREATENING FLASH FLOODS AND MUD SLIDES. +TOTAL RAIN ACCUMULATIONS OF 4 TO 8 INCHES...WITH MAXIMUM AMOUNTS OF +12 INCHES...ARE POSSIBLE ACROSS JAMAICA...THE CENTRAL AND EASTERN +PORTIONS OF CUBA...THE FLORIDA KEYS AND THE SOUTHERN PENINSULA OF +FLORIDA. TOTAL RAIN ACCUMULATIONS OF 2 TO 4 INCHES ARE POSSIBLE +OVER THE CENTRAL AND SOUTHEASTERN BAHAMAS. + +WIND...TROPICAL STORM CONDITIONS ARE SPREADING OVER PORTIONS OF THE +DOMINICAN REPUBLIC AND HAITI...WITH HURRICANE CONDITIONS POSSIBLE IN +HAITI. TROPICAL STORM CONDITIONS ARE EXPECTED OVER THE SOUTHEASTERN +BAHAMAS AND THE TURKS AND CAICOS ISLANDS TONIGHT...ARE EXPECTED +OVER THE CENTRAL BAHAMAS BY SATURDAY OR SATURDAY NIGHT...AND ARE +EXPECTED OVER THE NORTHWESTERN BAHAMAS BY SUNDAY. TROPICAL STORM +CONDITIONS ARE EXPECTED OVER EASTERN CUBA BY TONIGHT AND OVER +CENTRAL CUBA BY SATURDAY OR SATURDAY NIGHT. TROPICAL STORM +CONDITIONS ARE EXPECTED TO REACH NORTHWESTERN CUBA AND THE +NORTHWESTERN BAHAMAS BY SATURDAY NIGHT OR SUNDAY...AND SOUTH +FLORIDA AND THE FLORIDA KEYS ON SUNDAY. HURRICANE CONDITIONS ARE +POSSIBLE OVER THE FLORIDA KEYS...FLORIDA BAY...AND THE SOUTHERNMOST +FLORIDA PENINSULA BY SUNDAY EVENING. + +STORM SURGE...THE COMBINATION OF A STORM SURGE AND THE TIDE WILL +CAUSE NORMALLY DRY AREAS NEAR THE COAST TO BE FLOODED BY RISING +WATERS. THE WATER COULD REACH THE FOLLOWING DEPTHS ABOVE GROUND +IF THE PEAK SURGE OCCURS AT THE TIME OF HIGH TIDE... + +SOUTH FLORIDA INCLUDING THE FLORIDA KEYS...2 TO 4 FT +HISPANIOLA AND EASTERN CUBA...1 TO 3 FT +THE BAHAMAS AND TURKS AND CAICOS...1 TO 3 FT + +THE DEEPEST WATER WILL OCCUR ALONG THE IMMEDIATE COAST IN AREAS OF +ONSHORE FLOW. SURGE-RELATED FLOODING DEPENDS ON THE RELATIVE TIMING +OF THE SURGE AND THE TIDAL CYCLE...AND CAN VARY GREATLY OVER SHORT +DISTANCES. FOR INFORMATION SPECIFIC TO YOUR AREA...PLEASE SEE +PRODUCTS ISSUED BY YOUR LOCAL WEATHER SERVICE OFFICE. NEAR THE +COAST...THE SURGE WILL BE ACCOMPANIED BY DANGEROUS WAVES. + +SURF...DANGEROUS SURF AND RIP CURRENT CONDITIONS WILL AFFECT PUERTO +RICO...HISPANIOLA...THE BAHAMAS...THE TURKS AND CAICOS...EASTERN +AND CENTRAL CUBA...AND THE EAST COAST OF FLORIDA AND THE FLORIDA +KEYS DURING THE NEXT COUPLE OF DAYS. PLEASE CONSULT PRODUCTS FROM +YOUR LOCAL WEATHER OFFICE FOR MORE INFORMATION. + + +NEXT ADVISORY +------------- +NEXT INTERMEDIATE ADVISORY...200 AM EDT. +NEXT COMPLETE ADVISORY...500 AM EDT. + +$$ +FORECASTER STEWART""" + + ############################################################### + ### Advisory related methods + + def _archiveCurrentAdvisory(self): + ### Determine if all actions are canceled + allCAN = True + for vtecRecord in self._getAllVTECRecords(): + action = vtecRecord['act'] + #print "vtecRecord", vtecRecord + if action != "CAN": + allCAN = False + break + + self._currentAdvisory["AllCAN"] = allCAN + self._currentAdvisory["CreationTime"] = self._issueTime_secs + self._currentAdvisory["Transmitted"] = False + self._currentAdvisory["StormName"] = self._getStormNameFromTCP() + self._saveAdvisory("pending", self._currentAdvisory) + + def _loadAdvisory(self, advisoryName): + import json + + try: + jsonDict = self._getFileContents(LocalizationType.CAVE_STATIC, + self._site, + self._getAdvisoryFilename(advisoryName)) + + pythonDict = json.loads(jsonDict) + print "SARAH: File contents for", self._getAdvisoryFilename(advisoryName), ":" + print pythonDict + + # Only use transmitted advisories + if pythonDict["Transmitted"] == False: + return None + else: + return pythonDict + except Exception, e: + print "SARAH Load Exception for", self._getAdvisoryFilename(advisoryName), ":", e + return None + + def _saveAdvisory(self, advisoryName, advisoryDict): + import json + + try: + self._writeFileContents(LocalizationType.CAVE_STATIC, + self._site, + self._getAdvisoryPath() + advisoryName + ".json", + json.dumps(advisoryDict)) + print "SARAH: Wrote file contents for", (self._getAdvisoryPath() + advisoryName + ".json") + except Exception, e: + print "SARAH Save Exception for", (self._getAdvisoryPath() + advisoryName + ".json"), ":", e + + def _getAdvisoryPath(self): + return "gfe/tcvAdvisories/" + + def _getFileContents(self, loctype, siteID, filename): + pathManager = PathManagerFactory.getPathManager() + context = pathManager.getContextForSite(loctype, siteID) + localizationFile = pathManager.getLocalizationFile(context, filename) + pythonFile = File(localizationFile.getFile(), filename, 'r') + fileContents = pythonFile.read() + pythonFile.close() + + return fileContents + + def _writeFileContents(self, loctype, siteID, filename, contents): + pathManager = PathManagerFactory.getPathManager() + context = pathManager.getContextForSite(loctype, siteID) + localizationFile = pathManager.getLocalizationFile(context, filename) + pythonFile = File(localizationFile.getFile(), filename, 'w') + pythonFile.write(contents) + pythonFile.close() + localizationFile.save() + + def _getAdvisoryFilename(self, advisoryName): + year = time.gmtime(self._issueTime_secs).tm_year + advisoryFilename = self._getAdvisoryPath() + \ + self._getStormNameFromTCP().upper() + \ + str(year) + \ + advisoryName + \ + ".json" + return advisoryFilename + + ############################################################### + ### GUI related methods + + def _processVariableList(self, definition, parent): + # Get Definition variables + for key in definition.keys(): + exec "self._" + key + "= definition[key]" + + # Overview GUI + while True: + overviewDict = self._displayGUI() + if overviewDict is None: + return None + break + + # Consolidate information from GUI's + varDict = overviewDict + return varDict + + def _GUI_sizing_dict(self): + # This contains values that adjust the GUI sizing. + return { + "GUI_height_limit": 900, # limit to GUI height in canvas pixels + "charSize": 9, + } + + def _GUI1_configDict(self): + return { + # Order and inclusion of GUI1 buttons + # Each entry is (name of button in GUI code, desired label on GUI) + "buttonList":[ + ("Next","Next"), + ("Cancel","Cancel"), + ], + } + + def _font_GUI_dict(self): + return { + "headers": ("blue", ("Helvetica", 14, "bold")), + "instructions": (None, ("Helvetica", 12, "italic")), + } + + ############################################################### + ### TCV Statistics + + def _analysisList_TCV(self): + # Sample over 120 hours beginning at current time + analysisList = [ + # Wind Section + ("Wind", self.vectorModeratedMax, [6]), + ("WindGust", self.moderatedMax, [6]), + ("WindThreat", self.mostSignificantDiscreteValue), + ("pws34int", self.moderatedMax, [6]), + ("pws64int", self.moderatedMax, [6]), + ("pwsD34", self.moderatedMax), + ("pwsN34", self.moderatedMax), + ("pwsD64", self.moderatedMax), + ("pwsN64", self.moderatedMax), + + # Flooding Rain Section + ("QPF", self.accumSum, [72]), + ("FloodingRainThreat", self.mostSignificantDiscreteValue), + + # Tornado Section + ("TornadoThreat", self.mostSignificantDiscreteValue), + ] + + return analysisList + + def _intersectAnalysisList_TCV(self): + # The grids for the Surge Section will be intersected with a special edit area + analysisList = [ + ("InundationMax", self.moderatedMax, [6]), + ("InundationTiming", self.moderatedMax, [6]), + ("StormSurgeThreat", self.mostSignificantDiscreteValue), + ] + + return analysisList + + def moderated_dict(self, parmHisto, timeRange, componentName): + """ + Specifies the lower percentages and upper percentages of + data to be thrown out for moderated stats. + """ + # COMMENT: 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. + + # Get Baseline thresholds + dict = SampleAnalysis.SampleAnalysis.moderated_dict( + self, parmHisto, timeRange, componentName) + + # Change thresholds + dict["Wind"] = (0, 15) + dict["WindGust"] = (0, 15) + dict["pws34int"] = (0, 5) + dict["pws64int"] = (0, 5) + dict["pwsD34"] = (0, 5) + dict["pwsN34"] = (0, 5) + dict["pwsD64"] = (0, 5) + dict["pwsN64"] = (0, 5) + dict["InundationMax"] = (0, 5) + dict["InundationTiming"] = (0, 5) + return dict + + def threatKeyOrder(self): + return [None, "None", "Elevated", "Mod", "High", "Extreme"] + + def allowedHazards(self): + tropicalActions = ["NEW", "EXA", "CAN", "CON"] + return [ + ('HU.A',tropicalActions,'Hurricane'), + ('HU.W',tropicalActions,'Hurricane'), + ('SS.A',tropicalActions,'Surge'), + ('SS.W',tropicalActions,'Surge'), + ('TR.A',tropicalActions,'Tropical'), + ('TR.W',tropicalActions,'Tropical'), + ] + + def allowedHeadlines(self): + allActions = ["NEW", "EXA", "EXB", "EXT", "CAN", "CON", "EXP"] + return [ + ('FF.A', allActions, 'Flood'), # FLASH FLOOD WATCH + ('FA.A', allActions, 'Flood'), # FLOOD WATCH + ('TO.A', allActions, 'Convective'), # TORNADO WATCH + ] + + def _initializeAdvisories(self): + self._currentAdvisory = dict() + self._currentAdvisory['ZoneData'] = dict() + self._previousAdvisory = self._loadAdvisory("previous") + print "SARAH: loaded previous advisory =", self._previousAdvisory + self._previousPreviousAdvisory = self._loadAdvisory("previousPrevious") + print "SARAH: loaded previousPrevious advisory =", self._previousPreviousAdvisory + + def _sampleTCVData(self, argDict): + # Sample the data + editAreas = self._makeSegmentEditAreas(argDict) + cwa = self._cwa() + editAreas.append((cwa, cwa)) + + self._sampler = self.getSampler(argDict, + (self._analysisList_TCV(), self._timeRangeList, editAreas)) + + intersectAreas = self._computeIntersectAreas(editAreas, argDict) + + self._intersectSampler = self.getSampler(argDict, + (self._intersectAnalysisList_TCV(), self._timeRangeList, intersectAreas)) + + def _getTCVStats(self, argDict, segment, editAreaDict, timeRangeList): + # Get statistics for this segment + print "SARAH: issue time seconds =", self._issueTime_secs + print "SARAH: GMT issue time =", time.gmtime(self._issueTime_secs) + + editArea = editAreaDict[segment] + statList = self.getStatList(self._sampler, + self._analysisList_TCV(), + timeRangeList, + editArea) + + windStats = WindSectionStats(self, segment, statList, timeRangeList) + floodingRainStats = FloodingRainSectionStats(self, segment, statList, timeRangeList) + tornadoStats = TornadoSectionStats(self, segment, statList, timeRangeList) + + # The surge section needs sampling done with an intersected edit area + intersectEditArea = "intersect_"+editArea + intersectStatList = self.getStatList(self._intersectSampler, + self._intersectAnalysisList_TCV(), + timeRangeList, + intersectEditArea) + + stormSurgeStats = StormSurgeSectionStats(self, segment, intersectStatList, timeRangeList) + + return (windStats, stormSurgeStats, floodingRainStats, tornadoStats) + + def _initializeSegmentZoneData(self, segment): + # The current advisory will be populated when setting a section's stats + self._currentAdvisory['ZoneData'][segment] = { + "WindThreat": None, + "WindForecast": None, + "StormSurgeThreat": None, + "StormSurgeForecast": None, + "FloodingRainThreat": None, + "FloodingRainForecast": None, + "TornadoThreat": None, + } + + def _makeSegmentEditAreas(self, argDict): + areasList = self._segmentList + #print "areaList", areasList + editAreas = [] + self._editAreaDict = {} + for area in areasList: + self._editAreaDict[area] = area + editAreas.append((area, area)) + return editAreas + + def _determineSegments(self): + # Get the segments based on hazards "overlaid" with combinations file + + # Get the segments resulting from Hazards + #print "\nRaw Analyzed", self._hazardsTable.rawAnalyzedTable() + hazSegments = self.organizeHazards(self._hazardsTable.rawAnalyzedTable()) + #print "\nSegments from HazardsTable organizeHazards", hazSegments + + # Get the forecaster entered combinations + accessor = ModuleAccessor.ModuleAccessor() +# print "self._defaultEditAreas", self._defaultEditAreas + combos = accessor.variable(self._defaultEditAreas, "Combinations") + if combos is None: + LogStream.logVerbose("COMBINATION FILE NOT FOUND: " + self._defaultEditAreas) + return [], None + print "\nSegments from Zone Combiner", combos + # "Overlay" the forecaster-entered combinations onto the segments + segmentList = self._refineSegments(hazSegments, combos) + print "\nNew segments", segmentList + + # Instead of a segment being a group of zones, it will be just a single zone. + # So collapse this list of lists down to a list of zones (aka. segments) + segments = [] + for segment in segmentList: + segments += segment + + return segments + + def _refineSegments(self, hazSegments, combos): + """Break down each segment further according to combos given. + Make sure the resulting segments follow the ordering of the combos. + """ + if combos == []: + return hazSegments + newSegments = [] # list of lists + newAreas = [] + for combo, label in combos: + # Each combination will be tested to see if it can stay intact + # i.e. if all areas in the combo are in the same segment + # else split it into like segments + # + # segmentMapping is a list where each entry is + # the hazSegment in which the corresponding combo area appears. + # (We need to define self._segmentList for the mapping function + # to use) + self._segmentList = hazSegments + segmentMapping = map(self._findSegment, combo) + #print " segmentMapping", segmentMapping + + # segmentDict keys will be the hazSegments and + # we will gather all the areas of the combos that appear + # in each of these hazSegments + segmentDict = {} + keyList = [] + for areaName in combo: + #print " Adding", areaName + key = tuple(segmentMapping[combo.index(areaName)]) + if key == (): # If no hazard for area, do not include + continue + if key not in keyList: + keyList.append(key) + segmentDict.setdefault(key,[]).append(areaName) + #print " segmentDict", segmentDict + + # Keep track of the areas that we are including + for key in keyList: + segAreas = segmentDict[key] + newAreas = newAreas + segAreas + newSegments.append(segAreas) + #print " newSegments", newSegments + # Now add in the hazAreas that have not been accounted for + # in the combinations + for hazSegment in hazSegments: + newSeg = [] + for hazArea in hazSegment: + if hazArea not in newAreas: + newSeg.append(hazArea) + if newSeg != []: + newSegments.append(newSeg) + return newSegments + + def _findSegment(self, areaName): + for segment in self._segmentList: + if areaName in segment: + return segment + return [] + + +############################################################### +### TCV Statistics Classes + +class SectionCommonStats(): + def __init__(self, textProduct, segment): + self._textProduct = textProduct + self._segment = segment + + self._initializeAdvisories() + + self._maxThreat = None + + def _initializeAdvisories(self): + self._currentAdvisory = self._textProduct._currentAdvisory['ZoneData'][self._segment] + + self._previousAdvisory = None +# print "MATT textProduct._previousAdvisory = '%s'" % (textProduct._previousAdvisory) + if self._textProduct._previousAdvisory is not None: + self._previousAdvisory = self._textProduct._previousAdvisory['ZoneData'][self._segment] + +# print "MATT textProduct._previousPreviousAdvisory = '%s'" % \ +# (textProduct._previousPreviousAdvisory) + self._previousPreviousAdvisory = None + if self._textProduct._previousPreviousAdvisory is not None: + self._previousPreviousAdvisory = self._textProduct._previousPreviousAdvisory['ZoneData'][self._segment] + + def _updateThreatStats(self, tr, statDict, threatGridName): + print "SARAH: updateThreatStats for", threatGridName + threatLevel = self._textProduct.getStats(statDict, threatGridName) + if threatLevel is not None: + threatLevels = self._textProduct.threatKeyOrder() + print "SARAH: threatLevel =", threatLevel + print "SARAH: maxThreat =", self._maxThreat + if self._maxThreat is None or \ + threatLevels.index(threatLevel) > threatLevels.index(self._maxThreat): + print "SARAH: updating max threat to =", threatLevel + self._maxThreat = threatLevel + + def _calculateHourOffset(self, targetTime): + seconds = targetTime.unixTime() - self._textProduct._issueTime_secs + hour = int(round(seconds/60/60)) + if hour < 0: + hour = 0 + + return hour + + +class WindSectionStats(SectionCommonStats): + def __init__(self, textProduct, segment, statList, timeRangeList): + SectionCommonStats.__init__(self, textProduct, segment) + self._maxWind = None + self._maxGust = None + self._onset34Hour = None + self._end34Hour = None + self._onset64Hour = None + self._end64Hour = None + self._windowTS = None + self._windowHU = None + + self._setStats(statList, timeRangeList) + + class PwsXXintStats(): + max = None + onsetHour = None + + class PwsTXXStats(): + onsetHour = None + endHour = None + + class TimeInfo(): + onsetHour = None + endHour = None + + class EventsOccurring(): + pwsTXXEvent = False + windXXEvent = False + + def _setStats(self, statList, timeRangeList): + pws34intStats = self.PwsXXintStats() + pws64intStats = self.PwsXXintStats() + pwsT34Stats = self.PwsTXXStats() + pwsT64Stats = self.PwsTXXStats() + wind34timeInfo = self.TimeInfo() + wind64timeInfo = self.TimeInfo() + + events34 = self.EventsOccurring() + events64 = self.EventsOccurring() + + for period in range(len(statList)): + tr, _ = timeRangeList[period] + statDict = statList[period] + + self._updateStatsForPwsXXint(tr, statDict, "pws34int", pws34intStats) + self._updateStatsForPwsXXint(tr, statDict, "pws64int", pws64intStats) + + self._updateStatsForPwsTXX(tr, statDict, "pwsD34", "pwsN34", pwsT34Stats, events34, period) + self._updateStatsForPwsTXX(tr, statDict, "pwsD64", "pwsN64", pwsT64Stats, events64, period) + + wind = self._textProduct._getStatValue(statDict, "Wind", "Max", self._textProduct.VECTOR()) + if wind is not None: + if wind >= 34: + events34.windXXEvent = True + if wind >= 64: + events64.windXXEvent = True + else: + events64.windXXEvent = False + else: + events34.windXXEvent = False + events64.windXXEvent = False + + if self._maxWind is None or wind >= self._maxWind: + self._maxWind = wind + + self._updateWindTimeInfo(tr, wind34timeInfo, speed=34) + self._updateWindTimeInfo(tr, wind64timeInfo, speed=64) + + windGust = self._textProduct._getStatValue(statDict, "WindGust", "Max") + if windGust is not None: + if self._maxGust is None or windGust > self._maxGust: + self._maxGust = windGust + + self._updateThreatStats(tr, statDict, "WindThreat") + + #Tropical Storm + onsetEndInfo = self._computeWindOnsetAndEnd(wind34timeInfo, pws34intStats, pwsT34Stats) + self._onset34Hour = onsetEndInfo.onsetHour + self._end34Hour = onsetEndInfo.endHour + + nonEnding34Event = False + if events34.pwsTXXEvent and (wind34timeInfo.endHour is None or events34.windXXEvent): + nonEnding34Event = True + + print "SARAH: Tropical Storm Window:" + self._windowTS = self._createWindow("Tropical Storm", + self._onset34Hour, + self._end34Hour, + nonEnding34Event) + + #Hurricane + onsetEndInfo = self._computeWindOnsetAndEnd(wind64timeInfo, pws64intStats, pwsT64Stats) + self._onset64Hour = onsetEndInfo.onsetHour + self._end64Hour = onsetEndInfo.endHour + + nonEnding64Event = False + if events64.pwsTXXEvent and (wind64timeInfo.endHour is None or events64.windXXEvent): + nonEnding64Event = True + + print "SARAH: Hurricane Window:" + self._windowHU = self._createWindow("Hurricane", + self._onset64Hour, + self._end64Hour, + nonEnding64Event) + + self._currentAdvisory["WindThreat"] = self._maxThreat + self._currentAdvisory["WindForecast"] = self._maxWind + + def _updateStatsForPwsXXint(self, tr, statDict, gridName, pwsXXintStats): + pwsXXint = self._textProduct._getStatValue(statDict, gridName, "Max") + + if pwsXXint is not None: + if pwsXXintStats.max is None or pwsXXint > pwsXXintStats.max: + pwsXXintStats.max = pwsXXint + pwsXXintStats.onsetHour = self._calculateHourOffset(tr.startTime()) + + print "SARAH: Window Debug: pwsXXintStats gridName =", gridName + print "SARAH: Window Debug: pwsXXintStats pwsXXint =", pwsXXint + print "SARAH: Window Debug: pwsXXintStats tr =", tr + print "SARAH: Window Debug: pwsXXintStats onsetHour =", pwsXXintStats.onsetHour + + def _updateStatsForPwsTXX(self, tr, statDict, dayGridName, nightGridName, pwsTXXStats, events, period): + + # Convert this time to locatime + trStartLocalHour = time.localtime(tr.startTime().unixTime()).tm_hour + dayStartHour = self._textProduct.DAY() + nightStartHour = self._textProduct.NIGHT() + print "MATT _updateStatsForPwsTXX = %s localStartHr = %d" % (repr(tr), + trStartLocalHour) + print "MATT dayStart = %s nightStart = %s" % (repr(dayStartHour), + repr(nightStartHour)) + + pwsDXX = self._textProduct._getStatValue(statDict, dayGridName, "Max") + pwsNXX = self._textProduct._getStatValue(statDict, nightGridName, "Max") + maxPws = None + print "MATT pwsDXX = %s pwsNXX = %s " % (repr(pwsDXX), repr(pwsNXX)) + +# if pwsDXX is not None: +# print "SARAH: Window Debug: pwsTXXStats DAY" +# maxPws = pwsDXX +# elif pwsNXX is not None: +# print "SARAH: Window Debug: pwsTXXStats NIGHT" +# maxPws = pwsNXX + + # SARAH - if we are close to the end of a day/night period, the first + # period we would really want to consider would be the next period. + # This is hard-coded to 3 hours to prove the concept. + if (nightStartHour >= trStartLocalHour and \ + (nightStartHour - trStartLocalHour) <= 3) or pwsDXX is None: + print "MATT: Window Debug: pwsTXXStats NIGHT" + maxPws = pwsNXX + elif (dayStartHour >= trStartLocalHour and \ + (dayStartHour - trStartLocalHour) <= 3) or pwsNXX is None: + print "MATT: Window Debug: pwsTXXStats DAY" + maxPws = pwsDXX + + threshold34index = 0 + threshold64index = 1 + if maxPws is not None: + if "64" in dayGridName: + index = threshold64index + else: #if "34" + index = threshold34index + + threshold = None + thresholds = self.windSpdProb_thresholds() + if period == 0: + (thresholdLow, thresholdHigh) = thresholds[period][index] + threshold = thresholdLow + else: + if period >= 10: # SARAH: TODO - remove??? + period = 9 + threshold = thresholds[period][index] + + if maxPws > threshold: + events.pwsTXXEvent = True + + configuredEndTime = self._getCorrespondingConfiguredTime(tr.endTime(), isOnset = False) + pwsTXXStats.endHour = self._calculateHourOffset(configuredEndTime) + + print "SARAH: Window Debug: pwsTXXStats dayGridName =", dayGridName + print "SARAH: Window Debug: pwsTXXStats nightGridName =", nightGridName + print "SARAH: Window Debug: pwsTXXStats original tr =", tr + print "SARAH: Window Debug: pwsTXXStats maxPws =", maxPws + print "SARAH: Window Debug: pwsTXXStats endHour =", pwsTXXStats.endHour + + if pwsTXXStats.onsetHour is None: + configuredStartTime = self._getCorrespondingConfiguredTime(tr.startTime(), isOnset = True) + pwsTXXStats.onsetHour = self._calculateHourOffset(configuredStartTime) + + print "SARAH: Window Debug: pwsTXXStats dayGridName =", dayGridName + print "SARAH: Window Debug: pwsTXXStats nightGridName =", nightGridName + print "SARAH: Window Debug: pwsTXXStats original tr =", tr + print "SARAH: Window Debug: pwsTXXStats maxPws =", maxPws + print "SARAH: Window Debug: pwsTXXStats onsetHour =", pwsTXXStats.onsetHour + else: + events.pwsTXXEvent = False + + def _getCorrespondingConfiguredTime(self, gmtTime, isOnset): + dayStartHour = self._textProduct.DAY() + nightStartHour = self._textProduct.NIGHT() + + print "SARAH: gmtTime =", gmtTime + + gmtSeconds = gmtTime.unixTime() + localTime = time.localtime(gmtSeconds) + print "SARAH: localTime =", localTime + + localHour = localTime.tm_hour + print "SARAH: localHour =", localHour + + if isOnset: + print "SARAH: Window Debug: Adjusting start time" + else: + print "SARAH: Window Debug: Adjusting end time" + + newHour = None + if localHour < dayStartHour: + if isOnset: + # Subtract 24 hours to get to the previous day + newGmtTime = gmtTime - 24*60*60 + gmtSeconds = newGmtTime.unixTime() + localTime = time.localtime(gmtSeconds) + print "SARAH: new localTime =", localTime + + newHour = nightStartHour + else: + newHour = dayStartHour + elif dayStartHour <= localHour and localHour < nightStartHour: + if isOnset: + newHour = dayStartHour + else: + newHour = nightStartHour + else: + if isOnset: + newHour = nightStartHour + else: + # Add 24 hours to get to the next day + newGmtTime = gmtTime + 24*60*60 + gmtSeconds = newGmtTime.unixTime() + localTime = time.localtime(gmtSeconds) + print "SARAH: new localTime =", localTime + + newHour = dayStartHour + + print "SARAH: new localHour =", localHour + + newTimeTuple = localTime[:3] + (newHour,) + localTime[4:] + import calendar + seconds = calendar.timegm(newTimeTuple) + adjustedGmtTime = AbsTime(seconds) + print "SARAH: new local time =", adjustedGmtTime + + seconds = time.mktime(newTimeTuple) + adjustedGmtTime = AbsTime(seconds) + print "SARAH: new GMT time =", adjustedGmtTime + return adjustedGmtTime + + # SARAH - we don't want this here. Use the inherited version from the + # VectorRelatedPhrases module instead. This way, changes only need to be + # made in one place. + def windSpdProb_thresholds(self): + return [ + ((45.0, 80.0), (25.0, 60.0)), # Per 1 + (35.0, 20.0), # Per 2 + (30.0, 15.0), # Per 3 + (25.0, 12.5), # Per 4 + (22.5, 10.0), # Per 5 + (20.0, 8.0), # Per 6 + (17.5, 7.0), # Per 7 + (15.0, 6.0), # Per 8 + (12.5, 5.0), # Per 9 + (10.0, 4.0), # Per 10 + ] + + def _updateWindTimeInfo(self, tr, timeInfo, speed): + if self._maxWind is not None and self._maxWind >= speed: + timeInfo.endHour = self._calculateHourOffset(tr.endTime()) + + print "SARAH: Window Debug: timeInfo speed =", speed + print "SARAH: Window Debug: timeInfo maxWind =", self._maxWind + print "SARAH: Window Debug: timeInfo tr =", tr + print "SARAH: Window Debug: timeInfo endHour =", timeInfo.endHour + + if timeInfo.onsetHour is None: + timeInfo.onsetHour = self._calculateHourOffset(tr.startTime()) + + print "SARAH: Window Debug: timeInfo speed =", speed + print "SARAH: Window Debug: timeInfo maxWind =", self._maxWind + print "SARAH: Window Debug: timeInfo tr =", tr + print "SARAH: Window Debug: timeInfo onsetHour =", timeInfo.onsetHour + + def _computeWindOnsetAndEnd(self, windTimeInfo, pwsXXintStats, pwsTXXStats): + onsetEndInfo = self.TimeInfo() + + print "SARAH: Window Debug: windTimeInfo.onsetHour =", windTimeInfo.onsetHour + print "SARAH: Window Debug: pwsTXXStats.onsetHour =", pwsTXXStats.onsetHour + print "SARAH: Window Debug: pwsXXintStats.onsetHour =", pwsXXintStats.onsetHour + print "SARAH: Window Debug: windTimeInfo.endHour =", windTimeInfo.endHour + print "SARAH: Window Debug: pwsTXXStats.endHour =", pwsTXXStats.endHour + + if windTimeInfo.onsetHour is None: +# print "SARAH: Window Debug: windTimeInfo.onsetHour was None; using pwsTXXStats" +# windTimeInfo.onsetHour = pwsTXXStats.onsetHour +# print "SARAH: Window Debug: pwsTXXStats.onsetHour =", pwsTXXStats.onsetHour + + # Short-circuit this logic as a temporary measure. Basically, do + # not include a window if the deterministic winds do not support + # a particular threshold + onsetEndInfo.endHour = None + + if windTimeInfo.onsetHour is not None and pwsXXintStats.onsetHour is not None: + print "SARAH: Window Debug: windTimeInfo.onsetHour & pwsXXintStats.onsetHour not None; taking min" + onsetEndInfo.onsetHour = min(windTimeInfo.onsetHour, pwsXXintStats.onsetHour) + print "SARAH: Window Debug: min onsetHour =", onsetEndInfo.onsetHour + + if onsetEndInfo.onsetHour is not None: + if windTimeInfo.endHour is None: + print "SARAH: Window Debug: windTimeInfo.endHour was None; using pwsTXXStats" + onsetEndInfo.endHour = pwsTXXStats.endHour + print "SARAH: Window Debug: pwsTXXStats.endHour =", pwsTXXStats.endHour + elif pwsTXXStats.endHour is not None: + print "SARAH: windendHour =", windTimeInfo.endHour + print "SARAH: probendHour =", pwsTXXStats.endHour + onsetEndInfo.endHour = int(round(self._textProduct.average(windTimeInfo.endHour, pwsTXXStats.endHour))) + print "SARAH: endHour =", onsetEndInfo.endHour + return onsetEndInfo + + def _createWindow(self, windowName, onsetHour, endHour, nonEndingEvent): + window = "Window for " + windowName + " force winds: " + print "SARAH: window stats:" + print "SARAH: onsetHour =", onsetHour + print "SARAH: endHour =", endHour + print "SARAH: window nonEndingEvent =", nonEndingEvent + + if onsetHour is None: + + # SARAH - we do not want a statement of a non-existent window +# window += "None" + window = None + else: + startTime = AbsTime(self._textProduct._issueTime_secs + onsetHour*60*60) + if endHour is not None: + endTime = AbsTime(self._textProduct._issueTime_secs + endHour*60*60) + windowPeriod = self._textProduct.makeTimeRange(startTime, endTime) + else: + windowPeriod = self._textProduct.makeTimeRange(startTime, startTime + 1) + print "SARAH: window period =", windowPeriod + + startTimeDescriptor = "" + if onsetHour >= 18: + startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, resolution = 6) + elif 6 <= onsetHour and onsetHour < 18: + startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, resolution = 3) + + if endHour is None or nonEndingEvent: + if len(startTimeDescriptor) != 0: + window += "Begins " + startTimeDescriptor + else: + window += "None" + else: + connector = "through " + endTimeDescriptor = "the next few hours" + + if endHour >= 18: + endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, + useEndTime = True, + resolution = 6) + elif 6 <= endHour and endHour < 18: + endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, + useEndTime = True, + resolution = 3) + + if len(startTimeDescriptor) != 0: + connector = " " + connector + window += startTimeDescriptor + connector + endTimeDescriptor + + return window + + +class StormSurgeSectionStats(SectionCommonStats): + def __init__(self, textProduct, segment, intersectStatList, timeRangeList): + SectionCommonStats.__init__(self, textProduct, segment) + self._inundationMax = None + self._onsetSurgeHour = None + self._endSurgeHour = None + self._windowSurge = None + + self._setStats(intersectStatList, timeRangeList) + + def _setStats(self, statList, timeRangeList): + phishStartTime = None + phishEndTime = None + possibleStop = 0 + +# print "*"*100 +# print "MATT phishStartTime = %s phishEndTime = %s possibleStop = %d" % (str(phishStartTime), str(phishEndTime), possibleStop) + + for period in range(len(statList)): + tr, _ = timeRangeList[period] + statDict = statList[period] + + phishPeak = self._textProduct._getStatValue(statDict, "InundationMax", "Max") + if phishPeak is not None: + if self._inundationMax is None or phishPeak > self._inundationMax: + self._inundationMax = phishPeak + + curPhish = self._textProduct._getStatValue(statDict, "InundationTiming", "Max") +# print "MATT tr = %s" % (repr(tr)) +# print "MATT curPhish = '%s' possibleStop = %d" % (str(curPhish), possibleStop) +# print "MATT phishStartTime = %s phishEndTime = %s" % (str(phishStartTime), str(phishEndTime)) + + if curPhish is not None and possibleStop != 2: + if curPhish > 0: + if phishStartTime is None: + phishStartTime = tr.startTime() + possibleStop = 0 + phishEndTime = None + elif phishStartTime is not None: + possibleStop += 1 + + if phishEndTime is None: + phishEndTime = tr.startTime() + + self._updateThreatStats(tr, statDict, "StormSurgeThreat") + + self._windowSurge = "Window for Storm Surge Inundation: " + + if phishStartTime is None: + self._windowSurge += "None" + else: + self._onsetSurgeHour = self._calculateHourOffset(phishStartTime) + startTime = AbsTime(self._textProduct._issueTime_secs + self._onsetSurgeHour*60*60) + +# print "MATT surge startTime = %s self._onsetSurgeHour = %s " % (repr(startTime), self._onsetSurgeHour) + if phishEndTime is not None: + self._endSurgeHour = self._calculateHourOffset(phishEndTime) + endTime = AbsTime(self._textProduct._issueTime_secs + self._endSurgeHour*60*60) + windowPeriod = self._textProduct.makeTimeRange(startTime, endTime) + else: + windowPeriod = self._textProduct.makeTimeRange(startTime, startTime + 1) + print "SARAH: window period =", windowPeriod + + startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod) + + if phishEndTime is None: + self._windowSurge += "Begins " + startTimeDescriptor + elif phishStartTime == phishEndTime: + self._windowSurge += startTimeDescriptor + else: + endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, useEndTime = True) + + if self._onsetSurgeHour > 12: + self._windowSurge += startTimeDescriptor +\ + " through " +\ + endTimeDescriptor + else: + self._windowSurge += "through " + endTimeDescriptor + + self._currentAdvisory["StormSurgeThreat"] = self._maxThreat + if self._inundationMax is not None: + # Round so we don't store values like 1.600000023841858 + self._currentAdvisory["StormSurgeForecast"] = \ + int(self._inundationMax * 10.0) / 10.0 + + +class FloodingRainSectionStats(SectionCommonStats): + def __init__(self, textProduct, segment, statList, timeRangeList): + SectionCommonStats.__init__(self, textProduct, segment) + self._sumAccum = None + + self._setStats(statList, timeRangeList) + + def _setStats(self, statList, timeRangeList): + for period in range(len(statList)): + tr, _ = timeRangeList[period] + statDict = statList[period] + + stats = self._textProduct.getStats(statDict, "QPF") + if stats is not None: + for (value, tr) in stats: + + if value is not None: + if self._sumAccum is None: + self._sumAccum = value + else: + self._sumAccum += value + + self._updateThreatStats(tr, statDict, "FloodingRainThreat") + + self._currentAdvisory["FloodingRainThreat"] = self._maxThreat + if self._sumAccum is not None: + # Round so that we don't end up with stats like 4.03143835067749 + self._currentAdvisory["FloodingRainForecast"] = \ + self._textProduct.round(self._sumAccum, "Nearest", 0.5) + + +class TornadoSectionStats(SectionCommonStats): + def __init__(self, textProduct, segment, statList, timeRangeList): + SectionCommonStats.__init__(self, textProduct, segment) + + self._setStats(statList, timeRangeList) + + def _setStats(self, statList, timeRangeList): + for period in range(len(statList)): + tr, _ = timeRangeList[period] + statDict = statList[period] + + self._updateThreatStats(tr, statDict, "TornadoThreat") + + self._currentAdvisory["TornadoThreat"] = self._maxThreat + + +import Tkinter +class Common_Dialog(Dialog): + def __init__(self, parent, title, infoDict=None): + self._status = "Cancel" # exception, or user-cancels + self._tkObject_dict = {} # place to store reference to tk objects + self._varDict = {} # all end results must be saved here + self._infoDict = infoDict + self._parent = parent + Dialog.__init__(self, parent=None, title=title) + + def getVarDict(self): + return self._varDict + + def _makeRadioOrCheckList(self, master, label, elementList, default=None, + buttonSide=Tkinter.TOP, frameSide=Tkinter.LEFT, entryField=None, + headerFG=None, headerFont=None, boxType="radio", + listFrameRelief=Tkinter.GROOVE): + listFrame = Tkinter.Frame(master, relief=listFrameRelief, borderwidth=1) + + if label != "": + listLabel = Tkinter.Label(listFrame, text=label, fg=headerFG, font=headerFont) + listLabel.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=Tkinter.NO, padx=10) + + ivar = Tkinter.IntVar() + defaultIndex = 0 + ivarList = [] + for element in elementList: + index = elementList.index(element) + if type(element) is types.TupleType: + element, key = element + if boxType== "radio": + button = Tkinter.Radiobutton(listFrame, variable=ivar, text=element, value=index) + else: + ivar = Tkinter.IntVar() + if default is not None and element in default: ivar.set(1) + else: ivar.set(0) + button= Tkinter.Checkbutton(listFrame, variable=ivar, text=element) + ivarList.append(ivar) + button.pack(side=buttonSide, anchor=Tkinter.W, expand=Tkinter.YES, padx=4) + # Look for default + if element == default: + defaultIndex = index + + entryObject = None + if entryField is not None: + entryObject = self._makeEntry(listFrame, entryField) + # packing + listFrame.pack(side=frameSide, expand=Tkinter.NO, fill=Tkinter.Y) #, anchor=Tkinter.N) + #listFrame.pack(side=frameSide, expand=Tkinter.YES, fill=Tkinter.Y, anchor=Tkinter.N) + + if boxType == "radio": + ivar.set(defaultIndex) # set the default + if boxType == "check": + ivar = ivarList + return ivar, entryObject + + def _makeEntry(self, frame, text, width=20): + label = Tkinter.Label(frame, text=text) + label.pack(side=Tkinter.LEFT, fill=Tkinter.X, expand=Tkinter.NO) + entry = Tkinter.Entry(frame, relief=Tkinter.SUNKEN, width=width) + entry.pack(side=Tkinter.LEFT, fill=Tkinter.X, expand=Tkinter.NO) + return entry + + def cancelCB(self): + self._status = "Cancel" + self.cancel() + + def _entryName(self, name): + return name+"_entry" + + def _makeTuple(self,str): + str = re.sub('(?im)[^_a-z]', '', str) + return (str+":",str) + + def _setVarDict(self, key, value, options=None): + if options is not None: + value = options[value] + if type(value) is types.TupleType: + value = value[1] + self._varDict[self._makeTuple(key)] = value + + def status(self): + return self._status + + def buttonbox(self): + # override the existing ok/cancel button box, removing it. + # we do this so that we can attach our own hooks into the functions. + pass + + +######################################################### +# The following defintions are from TextProductCommon. # +# This is just bringing over the minimum amount needed. # +######################################################### +import DiscretePhrases +class TextProductCommon(DiscretePhrases.DiscretePhrases): + def __init__(self): + DiscretePhrases.DiscretePhrases.__init__(self) + + def setUp(self, areaDict): + self._areaDictionary = areaDict + + def hazardTimeZones(self, areaList): + ''' + Returns list of time zones for the starting time + and list of time zones for the ending time. + + The areaList provides a complete list of areas for this headline. + startT, endT are the hazard times. + ''' + + # get this time zone + thisTimeZone = os.environ.get('TZ') + if thisTimeZone is None: + thisTimeZone = 'GMT' + + zoneList = [] + areaDict = self._areaDictionary + + # check to see if we have any areas outside our time zone + for areaName in areaList: + if areaName in areaDict.keys(): + entry = areaDict[areaName] + if not entry.has_key('ugcTimeZone'): #add your site id + if thisTimeZone not in zoneList: + zoneList.append(thisTimeZone) + continue # skip it + timeZoneList = entry['ugcTimeZone'] + if type(timeZoneList) is not types.ListType: # a single value + timeZoneList = [str(timeZoneList)] # make it into a list + for timeZone in timeZoneList: + if timeZone not in zoneList: + zoneList.append(timeZone) + + # if the resulting zoneList is empty, put in our time zone + if len(zoneList) == 0: + zoneList.append(thisTimeZone) + + # if the resulting zoneList has our time zone in it, be sure it + # is the first one in the list + try: + index = zoneList.index(thisTimeZone) + if index != 0: + del zoneList[index] + zoneList.insert(0, thisTimeZone) + except: + pass + + return zoneList + + def getExpireTime(self, issueTime, purgeHours, vtecRecords, roundMinutes=15, + fixedExpire=0): + ''' + Given the issuance time, purgeHours, and the vtecRecords (with times converted to ms), + returns the appropriate expiration time. + + Expiration time is the earliest of the specified expiration time, 1 hr if a CAN code + is detected, or the ending time of ongoing events (CON, EXT, EXB, NEW). + The issueTime and expireTime are ints in milliseconds. + + @param issueTime in ms + @param purgeHours -- set time past issuance time. + The default for this is set by policy e.g. an FFA expires by default + in 8 hours. However, if there is a hazard end time earlier, then that + is used. + if -1, then hazard end time is to be used + @param vtecRecords in the segment with times converted to ms + @param roundMinutes + @param fixedExpire -- indicates to ignore the VTEC actions when computing the + expiration time + + ''' + if purgeHours > 0: + expireTime = issueTime + purgeHours * 3600 * 1000 + else: + expireTime = None + # Pick the earliest end time of the vtecRecords in the segment + for vtecRecord in vtecRecords: + if expireTime is None or vtecRecord.get('endTime') < expireTime: + expireTime = vtecRecord.get('endTime') + + if not fixedExpire: + canExpFound = 0 + activeFound = 0 + laterActive = None #later end time of all active events + for vtecRecord in vtecRecords: + action = vtecRecord.get('act') + if action in ['CAN','EXP']: + canExpFound = 1 + elif action in ['NEW','CON','EXT','EXB','EXA']: + activeFound = 1 + endTime = vtecRecord.get('endTime') + if endTime != 0: + if laterActive is not None: + laterActive = max(laterActive, endTime) + else: + laterActive = endTime + if laterActive is not None: + expireTime = min(expireTime, laterActive) + elif canExpFound and not activeFound: + expireTime = min(expireTime, issueTime+3600) #1hr from now + + #ensure expireTime is not before issueTime, and is at least 1 hour + if expireTime - issueTime < 3600: + expireTime = issueTime + 3600*1000 + + #round to next 'roundMinutes' + roundValue = roundMinutes*60*1000 #in milliseconds + delta = expireTime % roundValue # in milliseconds + baseTime = int(expireTime/roundValue)*roundValue + if delta/60*1000 >= 1: #add the next increment + expireTime = baseTime + roundValue + else: #within 1 minute, don't add the next increment + expireTime = baseTime + + return expireTime + + def getHeadlinesAndSections(self, vtecRecords, productID, issueTime): + ''' + Order vtec records and create the sections for the segment + + @param vtecRecords: vtecRecords for a segment + @param metaDataList: list of (metaData, hazardEvent) for the segment + @param productID: product ID e.g. FFA, CWF, etc. + @param issueTime: in seconds so that it compares to the vtec records + ''' + sections = [] + headlines = [] + headlineStr = '' + hList = copy.deepcopy(vtecRecords) + if len(hList): + if productID in ['CWF','NSH','OFF','GLF']: + hList.sort(self.marineSortHazardAlg) + else: + hList.sort(self.regularSortHazardAlg) + + while len(hList) > 0: + vtecRecord = hList[0] + + # Can't make phrases with vtecRecords with no 'hdln' entry + if vtecRecord['hdln'] == '': + hList.remove(vtecRecord) + continue + + # make sure the vtecRecord is still in effect or within EXP critiera + if (vtecRecord['act'] != 'EXP' and issueTime >= vtecRecord['endTime']) or \ + (vtecRecord['act'] == 'EXP' and issueTime > 30*60 + vtecRecord['endTime']): + hList.remove(vtecRecord) + continue # no headline for expired vtecRecords + + #assemble the vtecRecord type + hazStr = vtecRecord['hdln'] + headlines.append(hazStr) + #hazStr = self.convertToLower(hazStr) + + # if the vtecRecord is a convective watch, tack on the etn + phenSig = vtecRecord['phen'] + '.' + vtecRecord['sig'] + if phenSig in ['TO.A', 'SV.A']: + hazStr = hazStr + ' ' + str(vtecRecord['etn']) + + # add on the action + actionWords = self.actionControlWord(vtecRecord, issueTime) + hazStr = hazStr + ' ' + actionWords + + if len(hazStr): + # Call user hook + localStr = self.hazard_hook( + None, None, vtecRecord['phen'], vtecRecord['sig'], vtecRecord['act'], + vtecRecord['startTime'], vtecRecord['endTime']) # May need to add leading space if non-null + headlineStr = headlineStr + '...' + hazStr + localStr + '...\n' + + # always remove the main vtecRecord from the list + hList.remove(vtecRecord) + + return headlineStr, headlines + + def formatUGCs(self, ugcs, expireTime): + ''' + Create ugc header with expire time + 'COC123-112330-' + ''' + ugcStr = self.makeUGCString(ugcs) + ddhhmmTime = self.getFormattedTime( + expireTime/1000, '%d%H%M', shiftToLocal=0, stripLeading=0).upper() + ugcStr = ugcStr + '-' + ddhhmmTime + '-' + return ugcStr + + def getFormattedTime(self, time_secs, format='%I%M %p %Z %a %b %d %Y', + shiftToLocal=1, upperCase=0, stripLeading=1): + ''' + Return a text string of the given time in seconds in the given format + This method is used for product headers. + ''' + if time_secs == 0: + time_secs = time.time() + if shiftToLocal == 1: + curTime = time.localtime(time_secs) + else: + curTime = time.gmtime(time_secs) + localTime = time.localtime(time_secs) + zoneName = time.strftime('%Z',localTime) + timeStr = time.strftime(format, curTime) + if shiftToLocal == 0: + timeStr = string.replace(timeStr, zoneName, 'GMT') + if stripLeading==1 and (timeStr[0] == '0' or timeStr[0] == ' '): + timeStr = timeStr[1:] + if upperCase == 1: + timeStr = string.upper(timeStr) + timeStr = string.replace(timeStr, ' ', ' ') + return timeStr + + def formatUGC_names(self, ugcs, alphabetize=False, separator='-'): + ''' + For example: Saunders-Douglas-Sarpy-Lancaster-Cass-Otoe- + ''' + nameList = [] + for ugc in ugcs: + entry = self._areaDictionary.get(ugc) + nameList.append(entry.get('ugcName', ugc)) + if alphabetize: + nameList.sort() + return self.formatNameString(nameList, separator) + + def formatNameString(self, nameList, separator, state=None): + nameString = '' + for name in nameList: + nameString+= name + separator + if state: + nameString = nameString.rstrip(separator) + ' ('+state+') ' + return nameString + + def getVal(self, dictionary, key, default=None, altDict=None): + ''' + Convenience method to access dictionary keys and account for :skip and :editable suffixes + + @param dictionary + @param key, potentially without a suffix e.g. 'info' + @return the key value accounting for suffixes e.g. 'info:skip' + ''' + for dictKey in [key, key+':skip', key+':editable']: + if dictionary.get(dictKey): + return dictionary.get(dictKey) + if altDict and altDict.get(dictKey): + return altDict.get(dictKey) + return default + + def formatDatetime(self, dt, format='ISO', timeZone=None): + ''' + @param dt: datetime object + @param format: format string e.g. '%H%M %p %Z %a %e %b %Y' + @param zone: time zone e.g.'CST7CDT'. If None use UTC + @return datetime formatted with time zone e.g. '1400 PM CST Mon 12 Feb 2011' + ''' + import datetime + from dateutil import tz + # TODO REMOVE THIS BLOCK AS PART OF THE JSON REFACTOR. + if type(dt) is float: + dt = datetime.fromtimestamp(dt / 1000) + + from_zone = tz.tzutc() + new_time = dt.replace(tzinfo=from_zone) + if timeZone is not None: + to_zone = tz.gettz(timeZone) + new_time = new_time.astimezone(to_zone) + if format == 'ISO': + return new_time.isoformat() + else: + return new_time.strftime(format) + + def flush(self): + ''' Flush the print buffer ''' + os.sys.__stdout__.flush() + + def makeUGCString(self, ugcs): + ''' + Create the UGC string for product / segment headers. + ''' + # if nothing in the list, return empty string + if len(ugcs) == 0: + return '' + ugcList = copy.deepcopy(ugcs) + # Remove any blank UGC lines from the list + listsize=len(ugcList) + j=0 + while j < listsize: + if ugcList[j] == '': + del ugcList[j] + j=j+1 + + # Set up state variables and process initialize ugcStr with first ugc + # in ugcList + inSeq = 0 + ugcStr = ugcList[0] + curState = ugcStr[0:3] + lastNum = int(ugcList[0][3:]) + firstNum = 0 + lastUgc = ugcList[0] + + # By initializing properly we don't need the first item + ugcList.remove(ugcList[0]) + + for ugc in ugcList: + ugcState = ugc[:3] + ugcNumStr = ugc[3:] + num = int(ugcNumStr) + if ugcState == curState: + if num == lastNum + 1: + if inSeq > 0: + # Replace the last ugcNumStr in sequence with the + # current ugcNumStr + # e.g. 062>063 becomes 062>064 + ugcStr = ugcStr[:len(ugcStr)-3] + ugcNumStr + inSeq += 1 + else: + ugcStr += '>' + ugcNumStr + inSeq = 1 + else: # num != lastNum + 1 + ugcStr = self.checkLastArrow(inSeq, ugcStr) + inSeq = 0 # reset sequence when number not in sequence + ugcStr += '-' + ugcNumStr + else: + ugcStr = self.checkLastArrow(inSeq, ugcStr) + ugcStr += '-' + ugc + curState = ugcState + inSeq = 0 #reset sequence when switching states + lastNum = num + lastUgc = ugc + + # May have to clean up last arrow at the end + ugcStr = self.checkLastArrow(inSeq, ugcStr) + return ugcStr + + def checkLastArrow(self, inSeq, ugcStr): + ''' + Part of formatUGCs + ''' + if inSeq == 1: + # Change the last arrow to - since + # we only had 2 in the sequence e.g. + # 062>063 should be 062-063 + arrowIndex = ugcStr.rfind('>') + if arrowIndex >= 0: + ugcStr = ugcStr[:arrowIndex] + '-' + ugcStr[arrowIndex+1:] + return ugcStr + + + diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_NewHLS.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_NewHLS.py new file mode 100644 index 0000000000..a82528e364 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_NewHLS.py @@ -0,0 +1,2450 @@ +import GenericHazards +import string, time, os, re, types, copy, LogStream, collections +import ModuleAccessor, SampleAnalysis, EditAreaUtils +import math +import Tkinter +DEG_TO_RAD = 0.017453292 + + +from AbsTime import * +from StartupDialog import IFPDialog as Dialog +from com.raytheon.uf.common.dataplugin.gfe.reference import ReferenceData, ReferenceID +from com.raytheon.uf.common.dataplugin.gfe.grid import Grid2DBit as JavaGrid2DBit +AWIPS_ENVIRON = "AWIPS2" + +import Hazard_HLSTCV_Common + +class TextProduct(Hazard_HLSTCV_Common.TextProduct): + Definition = copy.deepcopy(GenericHazards.TextProduct.Definition) + + Definition["displayName"] = "None" + Definition["outputFile"] = "{prddir}/TEXT/HLS.txt" + Definition["database"] = "Official" # Source database + Definition["debug"] = 1 + Definition["mapNameForCombinations"] = "Zones_" + Definition["defaultEditAreas"] = "EditAreas_PublicMarine_" + Definition["showZoneCombiner"] = 1 # 1 to cause zone combiner to display + + Definition["productName"] = "LOCAL STATEMENT" + + Definition["fullStationID" ] = "" + Definition["wmoID" ] = "" + Definition["wfoCityState" ] = "" + Definition["pil" ] = "" + Definition["textdbPil" ] = "" + Definition["awipsWANPil" ] = "" + Definition["site"] = "" + Definition["wfoCity"] = "" + + Definition["areaName"] = "" #optional area name for product + Definition["areaDictionary"] = "AreaDictionary" + Definition["language"] = "english" + Definition["lineLength"] = 71 #Maximum line length + Definition["tabLength"] = 4 + + Definition["purgeTime"] = 8 # Default Expiration in hours if + Definition["includeZoneNames"] = 1 # Zone names will be included in the area header + Definition["includeIssueTime"] = 0 # Issue Time will be included in the area header + Definition["easPhrase"] = \ + "URGENT - IMMEDIATE BROADCAST REQUESTED" # Optional EAS phrase to be include in product header + Definition["callToAction"] = 1 + + def __init__(self): + Hazard_HLSTCV_Common.TextProduct.__init__(self) + + ##################################################################### + ##################################################################### + ### Organization of Formatter Code + + ############################################################### + ### MUST OVERRIDE DEFINITIONS !!! + ### _inlandAreas, _coastalAreas, _cwa + ############################################################### + + ############################################################### + ### Analysis Lists, SampleAnalysis Overrides and other + ### analysis related methods + ############################################################### + + ############################################################### + ### Hazards and Additional Hazards + ### allowedHazards is used for VTEC records and summary + ### headlines + ### allowedHeadlines are additional hazards reported in + ### certain sections + ############################################################### + + ############################################################### + ### HLS Product and Segment Parts Definition + ############################################################### + + ############################################################### + # CODE + ############################################################### + ### High level flow of formatter + ### generateForecast, initializeVariables, + ### determineSegments, determineTimeRanges, sampleData, + ### createProductDictionary, formatProductDictionary, + ### archiveCurrentAdvisory... + ############################################################### + + ############################################################### + ### Product Dictionary methods + ############################################################### + + ############################################################### + ### Area, Zone and Segment related methods + ############################################################### + + ############################################################### + ### Hazards related methods + ############################################################### + + ############################################################### + ### Sampling and Statistics related methods + ############################################################### + + ############################################################### + ### Time related methods + ############################################################### + + ############################################################### + ### Storm Information and TCP related methods + ############################################################### + + ############################################################### + ### Advisory related methods + ############################################################### + + ############################################################### + ### GUI related methods + ############################################################### + + + ############################################################### + ### MUST OVERRIDE DEFINITIONS !!! + + def _inlandAreas(self): + return [ +# "FLZ063", "FLZ066", "FLZ067", "FLZ068", "FLZ070", +# "FLZ071", "FLZ072", "FLZ073", "FLZ074", + ] + + def _coastalAreas(self): + return [ +# "FLZ069", "FLZ075", "FLZ168", "FLZ172", "FLZ173", "FLZ174", + ] + + def _cwa(self): + return "" #"MFL" + + def _cwa_descriptor(self): + return "" #"SOUTH FLORIDA" + + def _localReferencePoints(self): + # Give the name and lat/lon for each local reference point + return [ + #("West Palm Beach, FL", (26.71, -80.06)), + #("Fort Lauderdale, FL", (26.12, -80.15)), + #("Miami, FL", (25.77, -80.20)), + #("Miami Beach, FL", (25.81, -80.13)), + #("Naples, FL", (26.14, -81.80)), + #("Marco Island, FL", (25.94, -81.73)), + ] + + def _localReferencePoints_defaults(self): + # Give a list of the local reference point names to be + # turned on by default + return [] #["Miami, FL", "Naples, FL"] + + ############################################################### + ### Optional Overrides + + def _referencePointLimit(self): + # Give the number of reference points allowed to be chosen + # Also give a label (e.g. "two") for the GUI + return (2, "two") + + ############################################################### + ### Analysis Lists, SampleAnalysis Overrides and other + ### analysis related methods + + def _analysisList_HLS(self): + # Sample over 120 hours beginning at current time + analysisList = [ + # Wind Section + ("WindThreat", self.rankedDiscreteValue), + + # Flooding Rain Section + ("QPFtoFFGRatio", self.moderatedMax, [6]), + ("FloodingRainThreat", self.rankedDiscreteValue), + + # Tornado Section + ("TornadoThreat", self.rankedDiscreteValue), + ] + + return analysisList + + def _analysisList_HLS_WholeDomain(self): + # Sample over 120 hours beginning at current time + analysisList = [ + # Wind Section + ("Wind", self.vectorModeratedMax, [6]), + ] + + return analysisList + + def _intersectAnalysisList_HLS(self): + # The grids for the Surge Section will be intersected with a special edit area + analysisList = [ + ("InundationMax", self.moderatedMax, [6]), + ("StormSurgeThreat", self.rankedDiscreteValue), + ] + + return analysisList + + ############################################################### + ### TCV Product and Segment Parts Definition + + def _productParts_HLS(self, segment_vtecRecords_tuples): + partsList = [ + 'wmoHeader', + 'ugcHeader', + 'productHeader', + 'areaList', + 'summaryHeadlines', + 'newInformationHeader', + 'changesHazards', + 'currentHazards', + 'stormInformation', + 'situationOverview', + 'sigPotentialImpacts', + ] + + if self._ImpactsAnticipated: + for (_, sectionName) in self._IncludedImpacts: + partsList.append(sectionName) + + partsList.append('preparednessSection') + + if self._ImpactsAnticipated: + partsList.append('evacuationStatements') + partsList.append('otherPreparednessActions') + partsList.append('additionalSourcesInfo') + + partsList.append('nextUpdate') + partsList.append('endProduct') + + return { + 'partsList': partsList + } + + def _ugcHeader(self, productDict, productSegmentGroup, productSegment): + self._ugcs = self._allAreas() + productDict['ugcCodes'] = self._formatUGC_entries() + self._ugcHeader_value = self._tpc.formatUGCs(self._ugcs, self._expireTime) + productDict['ugcHeader'] = self._ugcHeader_value + + def _areaList(self, productDict, productSegmentGroup, productSegment): + productDict['areaList'] = "This product covers " + self._cwa_descriptor() + + def _formatUGC_entries(self): + ugcCodeList = [] + for ugc in self._ugcs: + areaDictEntry = self._areaDict.get(ugc) + if areaDictEntry is None: + # We are not localized correctly for the hazard + # So get the first dictionary entry + self.logger.info('Not Localized for the hazard area -- ugc' + ugc) + keys = self._areaDict.keys() + areaDictEntry = self._areaDict.get(keys[0]) + ugcEntry = collections.OrderedDict() + ugcEntry['state'] = areaDictEntry.get('stateAbbr') + ugcEntry['type'] = self._getUgcInfo(ugc, 'type') + ugcEntry['number'] = self._getUgcInfo(ugc, 'number') + ugcEntry['text'] = ugc + ugcEntry['subArea'] = '' + ugcCodeList.append(ugcEntry) + return ugcCodeList + + def _getUgcInfo(self, ugc, part='type'): + if part == 'type': + if ugc[2] == 'C': + return 'County' + else: + return 'Zone' + if part == 'number': + return ugc[3:] + + def _summaryHeadlines(self, productDict, productSegmentGroup, productSegment): + productDict['summaryHeadlines'] = self._headlines + + def _changesHazards(self, productDict, productSegmentGroup, productSegment): + if not self._ImpactsAnticipated: + productDict['changesHazards'] = [] + else: + productDict['changesHazards'] = self._changesHazardsList + + def _currentHazards(self, productDict, productSegmentGroup, productSegment): + if not self._ImpactsAnticipated: + productDict['currentHazards'] = [] + else: + productDict['currentHazards'] = self._currentHazardsList + + def _stormInformation(self, productDict, productSegmentGroup, productSegment): + stormInfoDict = dict() + if self._ImpactsAnticipated: + stormInfoDict['references'] = self._stormLocalReferences + stormInfoDict['location'] = self._stormLocation + stormInfoDict['intensity'] = self._stormIntensityTrend + stormInfoDict['movement'] = self._stormMovementTrend + productDict['stormInformation'] = stormInfoDict + + def _situationOverview(self, productDict, productSegmentGroup, productSegment): + productDict['situationOverview'] = "Succinctly describe the expected evolution of the event for the cwa; which hazards are of greater (or lesser) concern, forecast focus, etc." + + def _windSection(self, productDict, productSegmentGroup, productSegment): + sectionDict = dict() + sectionDict['title'] = "Wind" + sectionDict['impactRange'] = "" + sectionDict['impactLib'] = [] + sectionDict['additionalImpactRange'] = [] + sectionDict['variedImpacts'] = False + + impactMin = self._samplingDict['WindThreat']['impactMin'] + impactMax = self._samplingDict['WindThreat']['impactMax'] + impactRange = self._samplingDict['WindThreat']['impactRange'] + inputThreatDominant = self._samplingDict['WindThreat']['inputThreatDominant'] + + # Test the simplest case first + if impactMin == "none" and impactMax == "none": + sectionDict['impactRange'] = impactRange + sectionDict['variedImpacts'] = None + productDict['windSection'] = sectionDict + return + + # If there is only one impact across the entire CWA, and it is the max + if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": + sectionDict['impactRange'] = "Prepare for " + impactMax + " damage across " + self._cwa() + "." + # Handle the case where the impacts are not the same across the entire CWA + else: + sectionDict['variedImpacts'] = True + sectionDict['impactRange'] = "Prepare for " + impactMax + " damage " + self._frame("ENTER AREA DESCRIPTION") + "." + + sectionDict['impactLib'] = self._getPotentialImpactsStatements("Wind", self._impactCategoryToThreatLevel(impactMax)) + + # If there are additional areas + if impactRange != impactMax: + sectionDict['additionalImpactRange'].append("Prepare for " + + impactRange + + " damage across " + + self._frame("ENTER AREA DESCRIPTION") + ".") + + # If there is no impact across more than one half the area, include a statement for that as well + if inputThreatDominant == "None": + sectionDict['additionalImpactRange'].append("Elsewhere across " + self._cwa() + ", little to no impact is anticipated.") + + productDict['windSection'] = sectionDict + + def _surgeSection(self, productDict, productSegmentGroup, productSegment): + sectionDict = dict() + sectionDict['title'] = "Surge" + sectionDict['impactRange'] = "" + sectionDict['impactLib'] = [] + sectionDict['additionalImpactRange'] = [] + sectionDict['variedImpacts'] = True + + impactMin = self._samplingDict['StormSurgeThreat']['impactMin'] + impactMax = self._samplingDict['StormSurgeThreat']['impactMax'] + impactRange = self._samplingDict['StormSurgeThreat']['impactRange'] + inputThreatDominant = self._samplingDict['StormSurgeThreat']['inputThreatDominant'] + + # Test the simplest case first + if impactMin == "none" and impactMax == "none": + sectionDict['impactRange'] = impactRange + sectionDict['variedImpacts'] = None + productDict['surgeSection'] = sectionDict + return + + # See if we need to include the term "life-threatening" surge + # This corresponds to threat levels of Moderate, High and Extreme + lifeThreatening = "" + + if impactMax in ["significant", "extensive", "devastating", "catastrophic"]: + lifeThreatening = "life-threatening storm surge and " + + sectionDict['impactRange'] = "Prepare for " + \ + lifeThreatening + impactMax + \ + " damage in surge prone areas of " + self._cwa() + ", with the greatest impacts " + \ + self._frame("ENTER AREA DESCRIPTION") + "." + + sectionDict['impactLib'] = self._getPotentialImpactsStatements("Storm Surge", self._impactCategoryToThreatLevel(impactMax)) + + # Reexamine the impact range - we need to separate out "life-threatening" surge categories into a separate statement + impactParts = impactRange.split(" ") + + # Initialize a variable to keep the proper scope. This will hold any leftover surge categories + impactRangeRest = "" + + # Look at the high end of the range + if len(impactParts) == 3 and impactParts[2] in ["significant", "extensive", "devastating", "catastrophic"]: + # We have some "life-threatening" categories we need to split out - check the low end + if impactParts[0] in ["limited", "none"]: + # Make a new range to report + impactRange = "significant" + + if impactParts[2] != "significant": + impactRange += " to " + impactParts[2] + + impactRangeRest = impactParts[0] + + # Ensure the leftover impact range is set - just in case we need it + # This should only ever be "limited" in the case of surge under current policy + elif len(impactParts) == 1: + impactRangeRest = impactParts[0] + + # If there are additional life-threatening surge areas + if impactRange != impactMax and impactRange != impactMin: + sectionDict['additionalImpactRange'].append("Brace for " + + lifeThreatening + impactRange + + " damage " + self._frame("ENTER AREA DESCRIPTION")) + + # If there are additional areas + if impactRangeRest != impactMax: + sectionDict['additionalImpactRange'].append("Prepare for " + + impactRangeRest + + " damage from storm surge " + self._frame("ENTER AREA DESCRIPTION")) + + # If there is no impact across more than one half the area, include a statement for that as well + if inputThreatDominant == "None": + sectionDict['additionalImpactRange'].append("Elsewhere across " + self._cwa() + ", little to no impact is anticipated.") + + productDict['surgeSection'] = sectionDict + + def _floodingRainSection(self, productDict, productSegmentGroup, productSegment): + sectionDict = dict() + sectionDict['title'] = "Flooding Rain" + sectionDict['impactRange'] = "" + sectionDict['impactLib'] = [] + sectionDict['additionalImpactRange'] = [] + sectionDict['variedImpacts'] = False + + impactMin = self._samplingDict['FloodingRainThreat']['impactMin'] + impactMax = self._samplingDict['FloodingRainThreat']['impactMax'] + impactRange = self._samplingDict['FloodingRainThreat']['impactRange'] + inputThreatDominant = self._samplingDict['FloodingRainThreat']['inputThreatDominant'] + + # Test the simplest case first + if impactMin == "none" and impactMax == "none": + sectionDict['impactRange'] = impactRange + sectionDict['variedImpacts'] = None + productDict['floodingRainSection'] = sectionDict + return + + # If there is only one impact across the entire CWA, and it is the max + if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": + sectionDict['impactRange'] = "Prepare for " + impactMax + " flooding across " + self._cwa() + "." + # Handle the case where the impacts are not the same across the entire CWA + else: + sectionDict['variedImpacts'] = True + sectionDict['impactRange'] = "Prepare for " + impactMax + " flooding " + self._frame("ENTER AREA DESCRIPTION") + "." + + sectionDict['impactLib'] = self._getPotentialImpactsStatements("Flooding Rain", self._impactCategoryToThreatLevel(impactMax)) + + # If there are additional areas + if impactRange != impactMax: + sectionDict['additionalImpactRange'].append("Prepare for " + + impactRange + + " flooding impacts " + + self._frame("ENTER AREA DESCRIPTION") + ".") + + # If there is no impact across more than one half the area, include a statement for that as well + if inputThreatDominant == "None": + sectionDict['additionalImpactRange'].append("Elsewhere across " + self._cwa() + ", little to no impact is anticipated.") + + productDict['floodingRainSection'] = sectionDict + + def _tornadoSection(self, productDict, productSegmentGroup, productSegment): + sectionDict = dict() + sectionDict['title'] = "Tornadoes" + sectionDict['impactRange'] = "" + sectionDict['impactLib'] = [] + sectionDict['additionalImpactRange'] = [] + sectionDict['variedImpacts'] = False + + impactMin = self._samplingDict['TornadoThreat']['impactMin'] + impactMax = self._samplingDict['TornadoThreat']['impactMax'] + impactRange = self._samplingDict['TornadoThreat']['impactRange'] + inputThreatDominant = self._samplingDict['TornadoThreat']['inputThreatDominant'] + + # Test the simplest case first + if impactMin == "none" and impactMax == "none": + sectionDict['impactRange'] = impactRange + sectionDict['variedImpacts'] = None + productDict['tornadoSection'] = sectionDict + return + + # For tornadoes only, Cap at devastating + if impactMax in ["devastating", "catastrophic"]: + impactMax = "devastating" + if impactMin in ["devastating", "catastrophic"]: + impactMin = "devastating" + if impactRange in ["devastating", "catastrophic"]: + impactRange = "devastating" + + # If the max impact category is "catastrophic", and we lumped "devastating" in with it, ensure "devastating" is not + # leftover as the high end of the range + impactParts = impactRange.split(" ") # split up the impact range + + # If "devastating" is the high end of the range + if len(impactParts) == 3 and impactParts[2] == "devastating": + # If the first part is not "extensive" + if impactParts[0] != "extensive": + # Force the upper end to be 1 category lower + impactRange.replace("devastating", "extensive") + # Otherwise, the impact is just "extensive" + else: + impactRange = "extensive" + + # If there is only one impact across the entire CWA, and it is the max + if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": + sectionDict['impactRange'] = "Prepare for " + impactMax + " damage across " + self._cwa() + "." + # Handle the case where the impacts are not the same across the entire CWA + else: + sectionDict['variedImpacts'] = True + sectionDict['impactRange'] = "Prepare for " + impactMax + " damage " + self._frame("ENTER AREA DESCRIPTION") + "." + + sectionDict['impactLib'] = self._getPotentialImpactsStatements("Tornado", self._impactCategoryToThreatLevel(impactMax)) + + # If there are additional areas + if impactRange != impactMax: + sectionDict['additionalImpactRange'].append("Prepare for " + + impactRange + + " damage " + + self._frame("ENTER AREA DESCRIPTION") + ".") + + # If there is no impact across more than one half the area, include a statement for that as well + if inputThreatDominant == "None": + sectionDict['additionalImpactRange'].append("Elsewhere across " + self._cwa() + ", little to no impact is anticipated.") + + productDict['tornadoSection'] = sectionDict + + def _coastalHazardsSection(self, productDict, productSegmentGroup, productSegment): + productDict['coastalHazardsSection'] = self._frame("ENTER HERE A STATEMENT OF ANY ADDITIONAL HAZARDS OF CONCERN ALONG THE COAST SUCH AS RIP CURRENTS, HIGH WAVES, CONCERNS FOR BEACH EROSION ETC ETC IF NOT ALREADY DONE IN THE SURGE SECTION.") + + def _preparednessSection(self, productDict, productSegmentGroup, productSegment): + sectionDict = dict() + sectionDict['title'] = "Precautionary/Preparedness Actions" + + sectionDict['genericAction'] = None + if not self._ImpactsAnticipated: + sectionDict['genericAction'] = "It is always a good idea to check your preparedness plans so when and if the time comes during hurricane season, you are ready to execute them. A good resource is ready.gov." + + productDict['preparednessSection'] = sectionDict + + def _evacuationStatements(self, productDict, productSegmentGroup, productSegment): + evacuationDict = dict() + evacuationDict['title'] = "Evacuations" + + import TCVDictionary + evacuationDict['statements'] = TCVDictionary.EvacuationStatements + + productDict['evacuationStatements'] = evacuationDict + + def _otherPreparednessActions(self, productDict, productSegmentGroup, productSegment): + actionsDict = dict() + actionsDict['title'] = "Other Preparedness Information" + + import TCVDictionary + actionsDict['actions'] = TCVDictionary.OtherPreparednessActions + + productDict['otherPreparednessActions'] = actionsDict + + def _additionalSourcesInfo(self, productDict, productSegmentGroup, productSegment): + infoDict = dict() + infoDict['title'] = "Additional Sources of Information" + + import TCVDictionary + infoDict['sources'] = TCVDictionary.AdditionalSources + + productDict['additionalSourcesInfo'] = infoDict + + def _nextUpdate(self, productDict, productSegmentGroup, productSegment): + if self._NextUpdate == "LastIssuance" or not self._ImpactsAnticipated: + productDict['nextUpdate'] = "As it pertains to this event...this will be the last local statement issued by the National Weather Service in " + \ + self._cwa() + \ + " regarding the effects of tropical cyclone hazards upon the area." + elif self._NextUpdate == "Conditions": + productDict['nextUpdate'] = "The next local statement will be issued by the National Weather Service in " + \ + self._cwa() + \ + " as conditions warrant." + elif self._NextUpdate == "Enter": + productDict['nextUpdate'] = "The next local statement will be issued by the National Weather Service in " + \ + self._cwa() + \ + " around " + self._NextUpdate_entry.strip() + ", or sooner if conditions warrant." + + def _getPotentialImpactsStatements(self, elementName, maxThreat): + import TCVDictionary + potentialImpactStatements = TCVDictionary.PotentialImpactStatements + statements = potentialImpactStatements[elementName][maxThreat] + + return statements + + def _impactCategoryToThreatLevel(self, impactCategory): + if impactCategory == "catastrophic" or impactCategory == "devastating": + return "Extreme" + elif impactCategory == "extensive": + return "High" + elif impactCategory == "significant": + return "Mod" + elif impactCategory == "limited": + return "Elevated" + else: + return "None" + + ############################################################### + ### High level flow of formatter + + def generateForecast(self, argDict): + # Generate Text Phrases for a list of edit areas + + error = self._initializeVariables(argDict) + if error is not None: + return error + + error = self._initializeStormInformation() + if error is not None: + return error + + self._getStormInfo(argDict) + if self._stormName is None or self._stormName.strip() == "": + return "Could not determine the storm name" + + self._initializeHeadlines() + + self._initializeHazardsTable(argDict) + + self._determineHazardStates() + + # Determine time ranges + self._initializeTimeVariables(argDict) + self._determineTimeRanges(argDict) + + # Sample the data + self._initializeSamplingDict() + + previousAdvisory = self._loadAdvisory("previous") + if previousAdvisory is not None: + self._sampleTCVAdvisory(previousAdvisory) + else: + self._segmentList = self._determineSegments() + print "Segment Information: ", self._segmentList, "\n\n" + if len(self._segmentList) == 0: + return "NO HAZARDS TO REPORT" + + self._initializeAdvisories() + self._sampleTCVData(argDict) + for segment in self._segmentList: + self._initializeSegmentZoneData(segment) + self._getTCVStats(argDict, segment, self._editAreaDict, self._timeRangeList) + + self._sampleTCVAdvisory(self._currentAdvisory) + + self._sampleHLSData(argDict) + + for threatName in ['WindThreat', 'StormSurgeThreat', 'FloodingRainThreat', 'TornadoThreat']: + self._setHazardImpactCategories(threatName) + + # Create the product dictionary and format it to create the output + productDict = self._createProductDictionary() + productOutput = self._formatProductDictionary(productDict) + + return productOutput + + + def _determineHazardStates(self): + hazardTable = self._argDict["hazards"] + hazardsList = hazardTable.getHazardList(self._allAreas()) + self._currentHazardsList = [] + self._changesHazardsList = [] + for hazard in hazardsList: + if hazard['act'] == 'CON': + self._currentHazardsList.append(hazard) + else: + self._changesHazardsList.append(hazard) + + + ############################################################### + ### Sampling and Statistics related methods + + def _sampleHLSData(self, argDict): + editAreas = [(self._cwa(), self._cwa())] + + cwaSampler = self.getSampler(argDict, + (self._analysisList_HLS(), self._timeRangeList, editAreas)) + + statList = self.getStatList(cwaSampler, + self._analysisList_HLS(), + self._timeRangeList, + self._cwa()) + + for period in range(len(statList)): + statDict = statList[period] + for threatName in ['WindThreat', 'FloodingRainThreat', 'TornadoThreat']: + self._sampleThreatGrid(threatName, statDict) + + qpfToFfgRatio = self._getStatValue(statDict, "QPFtoFFGRatio", "Max") + decidingField = self._samplingDict['FloodingRainThreat']['decidingField'] + if decidingField is None or qpfToFfgRatio > decidingField: + self._samplingDict['FloodingRainThreat']['decidingField'] = qpfToFfgRatio + + print "SARAH: WindThreat =", self._samplingDict['WindThreat']['inputThreatDominant'] + print "SARAH: FloodingRainThreat =", self._samplingDict['FloodingRainThreat']['inputThreatDominant'] + print "SARAH: TornadoThreat =", self._samplingDict['TornadoThreat']['inputThreatDominant'] + + + + self._createWholeDomainEditArea(argDict) + editAreas = [("WholeDomain", "WholeDomain")] + wholeDomainSampler = self.getSampler(argDict, + (self._analysisList_HLS_WholeDomain(), self._timeRangeList, editAreas)) + + statList = self.getStatList(wholeDomainSampler, + self._analysisList_HLS_WholeDomain(), + self._timeRangeList, + "WholeDomain") + + for period in range(len(statList)): + statDict = statList[period] + maxWind = self._getStatValue(statDict, "Wind", "Max", self.VECTOR()) + decidingField = self._samplingDict['WindThreat']['decidingField'] + if decidingField is None or maxWind > decidingField: + self._samplingDict['WindThreat']['decidingField'] = maxWind + + + + editAreas = [(self._cwa(), self._cwa())] + intersectAreas = self._computeIntersectAreas(editAreas, argDict) + intersectSampler = self.getSampler(argDict, + (self._intersectAnalysisList_HLS(), self._timeRangeList, intersectAreas)) + + statList = self.getStatList(intersectSampler, + self._intersectAnalysisList_HLS(), + self._timeRangeList, + "intersect_" + self._cwa()) + + for period in range(len(statList)): + statDict = statList[period] + self._sampleThreatGrid('StormSurgeThreat', statDict) + + inundationMax = self._getStatValue(statDict, "InundationMax", "Max") + decidingField = self._samplingDict['StormSurgeThreat']['decidingField'] + if decidingField is None or inundationMax > decidingField: + self._samplingDict['StormSurgeThreat']['decidingField'] = inundationMax + + print "SARAH: StormSurgeThreat =", self._samplingDict['StormSurgeThreat']['inputThreatDominant'] + + def _createWholeDomainEditArea(self, argDict): + editAreaUtils = EditAreaUtils.EditAreaUtils() + editAreaUtils.setUp(None, argDict) + + gridLoc = editAreaUtils.getGridLoc() + grid2Dbit = JavaGrid2DBit( gridLoc.gridSize().x, gridLoc.gridSize().y) + grid2Dbit.setAllValues(1) + + refID = ReferenceID("WholeDomain") + refData = ReferenceData(gridLoc, refID, grid2Dbit) + editAreaUtils.saveEditAreas([refData]) + + def _sampleThreatGrid(self, threatName, statDict): + rankedThreatLevels = self.getStats(statDict, threatName) + print "SARAH: sampling", threatName + print "SARAH: sampleData: rankedThreatLevels =", rankedThreatLevels + dominantThreatLevel = self._getDominantThreatLevel(threatName, rankedThreatLevels) + + currentDominantThreatLevel = self._samplingDict[threatName]['inputThreatDominant'] + self._samplingDict[threatName]['inputThreatDominant'] = self._getHighestThreat(threatName, + dominantThreatLevel, + currentDominantThreatLevel) + + def _getDominantThreatLevel(self, threatName, rankedThreatLevels): + dominantLevelWithHighestRank = None + highestRank = None + + for (level, rank) in rankedThreatLevels: + if highestRank is None or rank > highestRank: + highestRank = rank + dominantLevelWithHighestRank = level + elif rank == highestRank: + dominantLevelWithHighestRank = self._getHighestThreat(threatName, + dominantLevelWithHighestRank, + level) + + return dominantLevelWithHighestRank + + def _getHighestThreat(self, threatName, threatLevel1, threatLevel2): + keyOrderDict = self.mostSignificantDiscrete_keyOrder_dict(None, None, None) + keyOrder = keyOrderDict[threatName] + + level1Index = keyOrder.index(threatLevel1) + level2Index = keyOrder.index(threatLevel2) + + if level1Index < level2Index: + return threatLevel2 + elif level1Index == level2Index: + return threatLevel1 + else: + return threatLevel1 + + def _initializeVariables(self, argDict): + # Get variables + error = self._getVariables(argDict) + if error is not None: + return error + + self._backupFullStationID = self._fullStationID + self._argDict = argDict + + argDict["definition"] = self._definition + + # Set up the areaDictionary for all to use + accessor = ModuleAccessor.ModuleAccessor() + self._areaDict = accessor.variable(self._areaDictionary, "AreaDictionary") + self._tpc = Hazard_HLSTCV_Common.TextProductCommon() + self._tpc.setUp(self._areaDict) + + return None + + def _initializeHeadlines(self): + if self._MainHeadline == "Enter": + self._headlines = [self._MainHeadline_entry] + elif self._MainHeadline == "UsePrev": + self._prevHLS = self.getPreviousProduct(self._textdbPil) + self._headlines = [self._grabHeadline(self._prevHLS)] + elif self._MainHeadline == "UseTCP": + try: + self._headlines = [self._grabHeadline(self._TCP)] + except: + self._headlines = [] + + def _initializeSamplingDict(self): + self._samplingDict = dict() + statsDict = dict() + statsDict['catastrophicThreshold'] = None + statsDict['decidingField'] = None + statsDict['inputThreatLow'] = None + statsDict['inputThreatHigh'] = None + statsDict['inputThreatDominant'] = None + statsDict['impactMin'] = None + statsDict['impactMax'] = None + statsDict['impactRange'] = None + + self._samplingDict['WindThreat'] = copy.copy(statsDict) + self._samplingDict['StormSurgeThreat'] = copy.copy(statsDict) + self._samplingDict['FloodingRainThreat'] = copy.copy(statsDict) + self._samplingDict['TornadoThreat'] = copy.copy(statsDict) + + self._samplingDict['WindThreat']['catastrophicThreshold'] = 137 # knots + self._samplingDict['StormSurgeThreat']['catastrophicThreshold'] = 14 # feet + self._samplingDict['FloodingRainThreat']['catastrophicThreshold'] = 3 # percent + + def _sampleTCVAdvisory(self, advisory): + print "SARAH: sampling TCV advisory!" + for zone in advisory["ZoneData"]: + print "Looking at zone", zone + for key in advisory["ZoneData"][zone]: + if "Threat" not in key: + continue + + print "Looking at key", key + + threatLevel = advisory["ZoneData"][zone][key] + print "Threat level =", threatLevel + if self._samplingDict[key]['inputThreatLow'] is None: + self._samplingDict[key]['inputThreatLow'] = threatLevel + if self._samplingDict[key]['inputThreatHigh'] is None: + self._samplingDict[key]['inputThreatHigh'] = threatLevel + + lowThreat = self._samplingDict[key]['inputThreatLow'] + highThreat = self._samplingDict[key]['inputThreatHigh'] + threatOrder = self.mostSignificantDiscrete_keyOrder_dict(None, None, None)[key] + + if threatOrder.index(threatLevel) < threatOrder.index(lowThreat): + lowThreat = threatLevel + if threatOrder.index(threatLevel) > threatOrder.index(highThreat): + highThreat = threatLevel + + print "low threat =", lowThreat + print "high threat =", highThreat + + self._samplingDict[key]['inputThreatLow'] = lowThreat + self._samplingDict[key]['inputThreatHigh'] = highThreat + + print "Sampling dict =", self._samplingDict + + def _setHazardImpactCategories(self, threatName): + inputThreatLow = self._samplingDict[threatName]['inputThreatLow'] + inputThreatHigh = self._samplingDict[threatName]['inputThreatHigh'] + inputThreatDominant = self._samplingDict[threatName]['inputThreatDominant'] + decidingField = self._samplingDict[threatName]['decidingField'] + catastrophicThreshold = self._samplingDict[threatName]['catastrophicThreshold'] + + impactMin = None + impactMax = None + impactRange = None + impactRangeMax = None + + # Determine lowest impact category + if inputThreatLow == "Extreme": + if threatName != "TornadoThreat" and decidingField >= catastrophicThreshold: + impactMin = "catastrophic" + else: + impactMin = "devastating" + elif inputThreatLow == "High": + impactMin = "extensive" + elif inputThreatLow == "Mod": + impactMin = "significant" + elif inputThreatLow == "Elevated": + impactMin = "limited" + else: + impactMin = "none" + + # Determine highest impact category + if inputThreatHigh == "Extreme": + if threatName != "TornadoThreat" and decidingField >= catastrophicThreshold: + impactMax = "catastrophic" + impactRangeMax = "devastating" + else: + impactMax = "devastating" + impactRangeMax = "extensive" + elif inputThreatLow == "High": + impactMax = "extensive" + impactRangeMax = "significant" + elif inputThreatLow == "Mod": + impactMax = "significant" + impactRangeMax = "limited" + elif inputThreatLow == "Elevated": + impactMax = "limited" + impactRangeMax = "none" + else: + impactMax = "none" + impactRangeMax = "none" + + # Determine dominant impact category for rest of CWA - No impact + if impactMin == "none" and impactMax == "none": + impactRange = "No impacts are anticipated at this time across " + self._cwa() + "." + # Otherwise, at least some impact will be experienced across the CWA + else: + # Do not permit the lowest category to be "None", if the highest category is also not "None" + # This is to avoid poor impact range wording in situations of tight gradients across a CWA + # (e.g. "None to High") + if impactMin == "none" and impactMax != "none": + impactMin = "limited" + + if impactMin == impactMax: + impactRange = impactMax + elif impactMin == impactRangeMax: + impactRange = impactRangeMax + else: + impactRange = impactMin + " to " + impactRangeMax + + self._samplingDict[threatName]['impactMin'] = impactMin + self._samplingDict[threatName]['impactMax'] = impactMax + self._samplingDict[threatName]['impactRange'] = impactRange + + def _grabHeadline(self, text=''): + # Get first headline found in text and return it as a string + + # Fixed pattern to grab headline (MHB 04/08/2009) + # See if there is a headline in this text + headlineSearch = re.findall("(?ism)^(\.{3}.+?\.{3}) *\n", text) + + self.debug_print("headlineSearch = %s" % (headlineSearch)) + + # If we found a headline + if len(headlineSearch) > 0: + + # Return the first cleaned-up headline string we found + return self._cleanText(headlineSearch[0]) + + # Otherwise, return an indicator there is no headline in this text + else: + return '' # Changed to an null string instead of None + # (MHB 04/08/2009) + + def _determineHazards(self, segments): + # Return a list of hazards from the given segments in the form: + # (key, landList, marineList, coastalList, inlandList) + # where key is (hdln, act, phen, sig) and the lists show which areas + # contain the hazard separated by category + hazAreaList = [] + for segment in segments: + hazardTable = self._argDict["hazards"] + hazards = hazardTable.getHazardList(segment) + for hazard in hazards: + action = hazard['act'] + hazAreaList.append((hazard, segment)) + # Consolidate hazards (there could be multiple segments with the same phen/sig/act) + hazardDict = {} + hazardList = [] + for hazard, segment in hazAreaList: + key = (hazard['hdln'], hazard['act'], hazard['phen'], hazard['sig']) + if key not in hazardDict.keys(): + hazardDict[key] = segment + hazardList.append(key) + else: + hazardDict[key] = hazardDict[key]+segment + + return hazardList + #print "\nhazardList", hazardList + + def SituationOverview(self, title, info): + t = title + un = self._Uncertainty + ec = self._EventContext + if ec == "Abbreviated": + hdlns = info.hazardHdlns + #print "\n Headlines" + reported = 0 + for hazardHdln in hdlns: + key = hazardHdln + #print "hazard", hazardHdln + hdln, act, phen, sig = key + if phen == "HU" and sig == "S": + continue + if act in self._ignoreActions(): + continue + if hdlns.index(hazardHdln) > 0: + t+= " AND " + t+= "A " + hdln + reported += 1 + if reported > 0: + if reported > 1: t+= " HAVE " + else: t+= " HAS " + t+="NOW BEEN ISSUED. " + t+="A MORE DETAILED STATEMENT WILL FOLLOW SHORTLY.\n" + + if ec in ["PreEvent","Watch","Warning"]: + if un=="High": + t+="IT IS VITAL THAT YOU DO NOT FOCUS ON THE EXACT FORECAST TRACK. " + t+="TO DO SO COULD RESULT IN BAD DECISIONS AND PLACE YOU OR THOSE YOU ARE " + t+="RESPONSIBLE FOR AT GREATER RISK. " + elif un == "Average": + t+="WHEN MAKING DECISIONS...DO NOT FOCUS ON THE EXACT FORECAST TRACK. " + + if ec != "Abbreviated": t+=self._frame("Succinctly describe the expected evolution of the event for the CWA & MAOR; which hazards are of greater (or lesser) concern, forecast focus, etc.")+ "\n" + + if ec in ["PreEvent", "Watch"]: + if info.anyLand: + t+="IT IS TOO EARLY TO PROVIDE EXACT WIND AND SURGE FORECAST VALUES FOR SPECIFIC LOCATIONS. " + damage = self._getCategoryDamage(info.maxWind_CWA_MAOR) + if damage.strip() != "": + t+="A GENERAL CONCERN SHOULD BE FOR THE POSSIBILITY OF "+damage+" SOMEWHERE WITHIN "\ + + self._cwa_descriptor() + ". " + + return t + + def _getStormInfo(self, argDict): + # Get the Storm information + self._stormType = "TROPICAL" + self._stormName = "CYCLONE" + self._stormTypeName = self._stormType + " " +self._stormName + + + stormDict = self._grabStormInfo(self._TCP) + self._stormName = stormDict.get("StormName", "") + self._stormType = stormDict.get("StormType", "") + self._stormTypeName = self._stormType + " " + self._stormName + self._decodeStormInfo(stormDict) + # Storm movement in mph and the stated movement trend + self._stormMovementTrend = self._expandBearings("Moving " + stormDict.get("StormMotion","")) + # Storm intensity in mph and the stated intensity trend. + self._stormIntensityTrend = "Storm Intensity " + stormDict.get("StormIntensity","") + + print "SARAH: BEGIN STORM INFORMATION" + print "storm dict =", stormDict + print "storm name =", self._stormName + print "type =", self._stormType + print "type name =", self._stormTypeName + print "time =", self._stormTime + print "lat =", self._stormLat + print "lon =", self._stormLon + print "location =", self._stormLocation + print "reference =", self._stormReference + print "references =", self._stormLocalReferences + print "movement trend =", self._stormMovementTrend + print "intensity trend =", self._stormIntensityTrend + print "SARAH: END STORM INFORMATION" + + def _grabStormInfo(self, tcp): + # Get the storm information from the selected TCP + # return a dictionary + # Initialize a dictionary to hold the information we want + dict = {"StormType" : "|* fill in storm type here *|", + "StormName" : "|* fill in storm name here *|", + "StormTime" : "|* Enter storm time *| ", + "StormLat": "", + "StormLon": "", + "StormReference": "", + "StormIntensity": "", + "StormMotion": "", + "StormInfo": "", + "StormCenter": "", + } + #======================================================================= + # If we got the latest public advisory + + if tcp is not None and len(tcp) > 0: + + #=================================================================== + # Try to determine the storm type and name automatically + + # Updated version to handle WFO GUM advisories. This pattern will + # handle multiple word names (including certain special characters) + # This is for the NHC format. + mndSearch = re.search("(?im)^.*?(HURRICANE|(SUB|POST.?)?TROPICAL " + + "(STORM|DEPRESSION|CYCLONE)|(SUPER )?TYPHOON|" + + "REMNANTS OF) ([A-Z0-9\-\(\) ]+?)" + + "(SPECIAL |INTERMEDIATE )?ADVISORY", tcp) + + # Display some debug info - if flag is set + self.debug_print("mndSearch = '%s'" % (mndSearch)) + + # If we found the storm type and name in the MND header + if mndSearch is not None: + + # Pick off the storm type and name + dict["StormType"] = mndSearch.group(1).strip() + dict["StormName"] = mndSearch.group(5).strip() + + #################################################################### + #################################################################### + # 12/15/2010 (MHB) - we should not need this anymore, but will + # leave it for the 2011 season as a fail-safe. + + # Look for the HPC format instead + else: + + mndSearch = re.search("(?im)^PUBLIC ADVISORY.+?FOR REMNANTS " + + "OF ([A-Z0-9\-\(\) ]+)", tcp) + + # If we found the storm type and name in the MND header + if mndSearch is not None: + + # Pick off the storm type and name + dict["StormType"] = "REMNANTS OF" + dict["StormName"] = mndSearch.group(1).strip() + + # end possible removal - 12/15/2010 (MHB) + #################################################################### + #################################################################### + + #=================================================================== + # Clean up the product for easier parsing + + tcp = self._cleanText(tcp) + + #=================================================================== + # Now try to grab the latest storm information + + # Look for the new NHC format first + summarySearch = re.search("(?is)SUMMARY OF (.+?)\.{3}.+?" + + "LOCATION\.{3}(.+?[NS]) +(.+?[WE]).+?" + + "(ABOUT .+?)MAXIMUM SUSTAINED WIND.+?" + + "(\d+ MPH).+?PRESENT MOVEMENT\.{3}" + + "(.+?)\.{3}", tcp) + + #-------------------------------------------------------------------- + # If we found the NHC summary section + + if summarySearch is not None: + + # Set aside some information we'll need later on + dict["StormTime"] = summarySearch.group(1).strip() + dict["StormLat"] = summarySearch.group(2).strip() + dict["StormLon"] = summarySearch.group(3).strip() + dict["StormReference"] = summarySearch.group(4).strip() + dict["StormIntensity"] = summarySearch.group(5).strip() + dict["StormMotion"] = summarySearch.group(6).strip() + + #================================================================ + # Use the remaining summary groups to contruct a paragraph + # similar to the "old" TCP format, and save that for later use + + # Start the paragraph with the advisory time + dict["StormCenter"] = "AT %s...THE CENTER OF " % \ + (dict["StormTime"]) + + # Now add some phrasing to maintain proper grammar, if needed + if dict["StormType"] == "REMNANTS OF": + dict["StormCenter"] = "%s THE" % (dict["StormCenter"]) + + # Now add the storm type and storm name + dict["StormCenter"] = "%s %s %s " % (dict["StormCenter"], + dict["StormType"], + dict["StormName"]) + + # Now add the storm position + dict["StormCenter"] = \ + "%s WAS LOCATED AT LATITUDE %s...LONGITUDE %s." % \ + (dict["StormCenter"], dict["StormLat"], dict["StormLon"]) + + #---------------------------------------------------------------- + # Now add the primary NHC geographic reference + + # Get all the NHC references - starting with the word 'ABOUT' + # after the first one + referenceIndex = dict["StormReference"][4:].find('ABOUT') + + # Assume we only have one NHC reference point by default + nhcReference = dict["StormReference"] + +## print "referenceIndex = ", referenceIndex + + # If we have more than one NHC reference point + if referenceIndex != -1: + + # Adjust this index to account for the first 'ABOUT' + referenceIndex += 4 + + # Only keep the first NHC reference location + nhcReference = dict["StormReference"][:referenceIndex] + + # Convert any abbreviated bearings to full words + nhcReference = self._expandBearings(nhcReference) + + # Add only first one to the summary paragraph for brevity + dict["StormCenter"] = "%s THIS WAS %s. " % \ + (dict["StormCenter"], + self._removeKM(nhcReference.strip())) + + #---------------------------------------------------------------- + # Add the maximum sustained wind speed phrase + + dict["StormCenter"] = "%s MAXIMUM SUSTAINED WINDS WERE %s." % \ + (dict["StormCenter"], + self._removeKM(dict["StormIntensity"])) + + #---------------------------------------------------------------- + # Now add the storm motion + + dict["StormCenter"] = "%s THE STORM MOTION WAS %s." % \ + (dict["StormCenter"], + self._removeKM(dict["StormMotion"])) + + #################################################################### + #################################################################### + # 12/15/2010 (MHB) - we should not need this anymore, but will + # leave it for the 2011 season as a fail-safe. + #-------------------------------------------------------------------- + # Search the product for the legacy storm info section - in case + # the new NHC style was not found + + stormInfoSearch = \ + re.search('(?is)(AT +(\d+ +[AP]M [AECMPH][DS]T)' + + '\.{3}\d+ *(Z|UTC)\.{3}THE (CENTER|REMNANTS|EYE) .+)', + tcp) + + # Display some debug info - if flag is set + self.debug_print("storminfoSearch = '%s'" % (stormInfoSearch)) +## print stormInfoSearch.groups() + + # If we found the storm info section of the product + if stormInfoSearch is not None: +# for group in stormInfoSearch.groups(): +# print '\t' + '-'*50 +# print "%s\n" % (group) + + # Clean this section up a bit. Keep each paragraph separate + # by a single , but remove all others as well as extra + # spaces. Then store this text in the TCP dictionary + dict["StormInfo"] = stormInfoSearch.group(1).strip() + + # Set aside the first paragraph of the storm info since it + # contains the TPC-provided reference point - if we haven't + # already found this information + if len(dict["StormCenter"].strip()) == 0: + dict["StormCenter"] = dict["StormInfo"].split('\n')[0] + + # If we have not already found the advisory time - get it from + # the legacy format + if dict["StormTime"] == "|* Enter storm time *| ": + dict["StormTime"] = stormInfoSearch.group(2).strip() + + # Set aside the first paragraph of the storm info since it + # contains the TPC-provided reference point - if we haven't + # already found this information + if len(dict["StormCenter"].strip()) == 0: + dict["StormCenter"] = dict["StormInfo"].split('\n')[0] + + #=================================================================== + # Now try to grab the repeated storm information summary + + repeatInfo = re.search("(?is)(\.{3}SUMMARY.+?\.)\n *\n", + tcp) + # If we cannot find the summary, try to find a "repeating" section + if repeatInfo is None: + repeatInfo = re.search("(?is)(REPEATING.+?\.)\n *\n", tcp) +## print repeatInfo + + # If we found the repeated storm information summary + if repeatInfo is not None: + + # Clean up this paragraph + summary = repeatInfo.group(1).strip() + + #=============================================================== + # Now try to grab the latest storm location - if we need it + + if dict["StormLat"] == "" or dict["StormLon"] == "": + + # Search the product for the storm location section + locationSearch = \ + re.search('(?is).+LOCATION.*?(\d+\.\d+ *N).+?' + + '(\d+\.\d+ *[EW])', summary) + + # Display some debug info - if flag is set + self.debug_print("locationSearch = '%s'" % (locationSearch)) +## print locationSearch.groups() + + # If we found the storm location section of the product + if locationSearch is not None: + + # Pick off the storm latitude and longitude + dict["StormLat"] = locationSearch.group(1).strip() + dict["StormLon"] = locationSearch.group(2).strip() + + #=============================================================== + # Now try to grab the latest storm intensity - if we need it + + if dict["StormIntensity"] == "": + + # Search the product for the storm intensity section + intensitySearch = \ + re.search('(?i).+MAXIMUM SUST.+?(\d+ *MPH)', summary) + + # Display some debug info - if flag is set + self.debug_print("intensitySearch = '%s'" % + (intensitySearch)) + + # If we found the storm intensity section of the product + if intensitySearch is not None: + + # Pick off the storm intensity + dict["StormIntensity"] = intensitySearch.group(1).strip() + + #=============================================================== + # Now try to grab the latest storm motion - if we need it + + if dict["StormMotion"] == "": + + # Search the product for the storm motion section + motionSearch = re.search('(?i).+MOVEMENT\.{3}(.+?\d+ MPH)', + summary) + if motionSearch is None: + motionSearch = re.search('(?i).+MOVEMENT(.+?\d+.+?)\.', + summary) + + # Display some debug info - if flag is set + self.debug_print("motionSearch = '%s'" % (motionSearch)) + + # If we found the storm motion section of the product + if motionSearch is not None: + + # Pick off the storm motion + motion = motionSearch.group(1).strip() + + # Fix the motion (i.e no '...') + dict["StormMotion"] = re.sub('(?i)\.{3}', ' the ', motion) + + # end possible removal - 12/15/2010 (MHB) + #################################################################### + #################################################################### + + #======================================================================== + # Display final decoded information from TCP + +## print "\n\n" + "*" *80 +## print "Final TCP Info...\n" +## print 'dict["StormType"] = ', dict["StormType"] +## print 'dict["StormName"] = ', dict["StormName"] +## print 'dict["StormTime"] = ', dict["StormTime"] +## print 'dict["StormLat"] = ', dict["StormLat"] +## print 'dict["StormLon"] = ', dict["StormLon"] +## print 'dict["StormReference"] = ', dict["StormReference"] +## print 'dict["StormIntensity"] = ', dict["StormIntensity"] +## print 'dict["StormMotion"] = ', dict["StormMotion"] +## print 'dict["StormInfo"] = ', dict["StormInfo"] +## print 'dict["StormCenter"] = ', dict["StormCenter"] + + # Return the dictionary will all the information we found in the TCP + return dict + + def _decodeStormInfo(self, stormDict): + self._stormTime = "|* Enter Storm Time *| " + self._stormLat = "|* Enter Storm Lat *| " + self._stormLon = "|* Enter Storm Lon *| " + self._stormLocation = "|* Enter Storm Location *| " + self._stormReference = "" + self._stormLocalReferences = "" + para = stormDict.get("StormCenter", "") + # print "\npara", len(para), para + if len(para)<= 0: + return + + # Create the time string + self._stormTime = self._formatLocalTime(para, self._allAreas()) + + # Find stormLat, stormLon and stormLocation + # e.g. LATITUDE 15.7 NORTH...LONGITUDE 80.0 WEST + stormLocation ="" + stormLat = None + stormLon = None + + # Make a pattern to find the latest storm location + coordPtn = re.compile("(?i)(LATITUDE ([\d\.]+) ?((N|S)(O[RU]TH)?))..." + + "(AND )?(LONGITUDE ([\d\.]+) ?((W|E)([AE]ST)?)).+?") +## + "OR ((ABOUT )?.+)") + + # Make a pattern to find the NHC reference location + refPtn = re.compile("(?i)(WAS|OR) ((ABOUT )?\d+ MILES.+?" + + "(NORTH|SOUTH|EAST|WEST).+?)\.") + + # Try to find these patterns in the text + coordPtnMatch = coordPtn.search(para) +## print "+" * 90 +## print "\ncoordinate search..." +## print coordPtnMatch.groups() + + refPtnMatch = refPtn.search(para) +## print "\nreference search..." +## print refPtnMatch.groups() + + # If we found the coordinates we were after + if coordPtnMatch is not None: + + # If we have the correct paragraph, set aside the latitude and + # longitude info as numbers + self._stormLat = float(coordPtnMatch.group(2)) + self._stormLon = float(coordPtnMatch.group(8)) # was 7 + + # Adjust latitude and longitude as need for "other" hemispheres + if coordPtnMatch.group(4) in ["S", "s"]: + self._stormLat *= -1.0 + + if coordPtnMatch.group(10) in ["W", "w"]: + self._stormLon *= -1.0 + + # Construct the storm location pair and remove the "LATITUDE " and "LONGITUDE " text + self._stormLocation = (coordPtnMatch.group(1)[9:], coordPtnMatch.group(7)[10:]) + + # If we found the primary NHC reference we were after + if refPtnMatch is not None: + + # Set aside all the geographic reference text +## stormReference = coordPtnMatch.group(11) + stormReference = refPtnMatch.group(2) + + # Watch out for some grammar gotchas with this reference + stormReference = re.sub("(?i)^(WAS|OR) ", "", stormReference) + + # See if there are multiple geographic references + if re.search('(?i) and ', stormReference) is not None: + + # Yes there are multiple references, so only keep the + # first one + stormReference = re.sub("(?i) AND .+", "", stormReference) + + # Also remove any metric distances + self._stormReference = self._removeKM(stormReference) + + # Miles/km from chosen local reference + self._stormLocalReferences = self._calcLocalReferences( + self._stormLat, self._stormLon) + +## print "stormLocalRefs = ", self._stormLocalReferences + + # Compare the NHC reference to the local references + for localRef in self._stormLocalReferences: + +## print self._stormReference, localRef + + # Get the locations from these statements + nhcRef = re.search('(?i)(north|south|east|west) of (.+)', + self._stormReference) + testRef = re.search('(?i)(north|south|east|west) of (.+)', + localRef) + +## print "nhcRef = '%s'\ttestRef = '%s'" % (nhcRef.group(2), testRef.group(2)) + + # If we have a local reference that matches the national + # center reference + if testRef is not None and nhcRef is not None and \ + re.search("(?i)%s" % (testRef.group(2).strip()), + nhcRef.group(2)) is not None: + + # Do not include the national reference + self._stormReference = "" + + def _expandBearings(self, text): + # Convert any abbreviated bearings to full words + text = text.replace(' N ', ' NORTH ') + text = text.replace(' NNE ', ' NORTH-NORTHEAST ') + text = text.replace(' NE ', ' NORTHEAST ') + text = text.replace(' ENE ', ' EAST-NORTHEAST ') + text = text.replace(' E ', ' EAST ') + text = text.replace(' ESE ', ' EAST-SOUTHEAST ') + text = text.replace(' SE ', ' SOUTHEAST ') + text = text.replace(' SSE ', ' SOUTH-SOUTHEAST ') + text = text.replace(' S ', ' SOUTH ') + text = text.replace(' SSW ', ' SOUTH-SOUTHWEST ') + text = text.replace(' SW ', ' SOUTHWEST ') + text = text.replace(' WSW ', ' WEST-SOUTHWEST ') + text = text.replace(' W ', ' WEST ') + text = text.replace(' WNW ', ' WEST-NORTHWEST ') + text = text.replace(' NW ', ' NORTHWEST ') + text = text.replace(' NNW ', ' NORTH-NORTHWEST ') + + return text + + # Modified 12/15/2010 (MHB) - modified to recognize the new way NHC will + # present metric speeds. Will continue to recognize the "old" way for + # testing purposes as well. + def _removeKM(self, words): + # Remove references to KM e.g. + # 420 KM... 100 KM/HR... + +# print "words = '%s'" % (words) + + kmSearch = re.compile("\.\.\. *[0-9]+ +(KM|KM/HR?) *\.?\.?\.?") + + # Replace metric reference with a space to keep words from mashing + # together. + words = kmSearch.sub(" ", words) + + # Make sure we don't have any double space issues with this text + doubleSpaces = re.findall(' +', words) + for doubleSpace in doubleSpaces: + words = re.sub(doubleSpace, ' ', words) + +# print "\tfinal words = '%s'" % (words) + return words + + def _cleanText(self, text=''): + # Cleans up text for easier string searches, but retains paragraphs + + # Replace all single characters with a space + text = re.sub("\n(?! *\n)", " ", text) + + # Ensure all text is only single-spaced + text = re.sub(" +", " ", text) + + # Remove all spaces at the start of a new paragraph + text = re.sub("(?m)^ +", "", text) + + # Do not allow any spaces after an ellipsis + text = re.sub("\.{3} +", "...", text) + + # Finally, ensure the paragraphs are put back + text = re.sub("\n", "\n\n", text) + + # Return the cleaned-up text + return text + + def _formatLocalTime(self, para, areas): + # Create a time string in local time + # e.g. 2 AM EDT + # Get the Z time hour + timeSearch = re.compile("...([0-9]+) *(Z|UTC)...") + timeStr = timeSearch.search(para) + +## gmtStr = para[timeStr.start():timeStr.end()] +## gmt = gmtStr.strip("...").replace("Z","") +## gmtHour = int(gmt)/100 + + # This code could bomb in the unlikely event we don't find a UTC + # time. We should probably add some kind of default hour here, + # keyed off the current hour, to prevent this. (MHB) + try: + # Convert the hour portion of the time string to an integer + gmtHour = int(timeStr.group(1)[:2]) + except: + gmtHour = time.gmtime().tm_hour + + gmtTR = self.createTimeRange(gmtHour, gmtHour+1, "Zulu") + gmtTime = gmtTR.startTime().unixTime() + + # Now make a string for each time zone + zoneList = self._getTimeZoneList(areas) + timeStrs = [] + timeDesc = "" + for timeZone in zoneList: + timeStr = self.formatTimeString(gmtTime, "%I %p %Z ", timeZone) + timeStr = string.replace(timeStr, " ", " ") + timeStr = string.strip(timeStr) + timeStr = timeStr.lstrip("0") + if timeStr not in timeStrs: + if len(timeStrs) > 0: + timeDesc += "...OR " + timeStrs.append(timeStr) + timeDesc += timeStr + return timeDesc + + def _getTimeZoneList(self, areaList): + # NOTE -- this code was taken from the middle of getAreaHeader + # in Header.py -- it really should be put back in and used + # in Header.py, but to avoid confusion, I'm repeating it here + # get this time zone + thisTimeZone = os.environ["TZ"] + zoneList = [] + # check to see if we have any areas outside our time zone + for areaName in areaList: + if areaName in self._areaDict.keys(): + entry = self._areaDict[areaName] + if not entry.has_key("ugcTimeZone"): #add your site tz + if thisTimeZone not in zoneList: + zoneList.append(thisTimeZone) + continue # skip this entry + timeZoneList = entry["ugcTimeZone"] + if type(timeZoneList) is types.StringType: # a single value + timeZoneList = [timeZoneList] # make it into a list + for timeZone in timeZoneList: + if timeZone not in zoneList: + zoneList.append(timeZone) + # if the resulting zoneList is empty, put in our time zone + if len(zoneList) == 0: + zoneList.append(thisTimeZone) + # if the resulting zoneList has our time zone in it, be sure it + # is the first one in the list + try: + index = zoneList.index(thisTimeZone) + if index != 0: + del zoneList[index] + zoneList.insert(0, thisTimeZone) + except: + pass + return zoneList + + def _calcLocalReferences(self, lat0, lon0): + localRefs = [] + refList = self._LocalReferencePoints + #refList.append(("Grand Cayman", (19.2, -81.4))) + # Limit reference points + refLimit = self._referencePointLimit() + if len(refList) > refLimit: + refList = refList[0:refLimit] + for label, latLon in refList: + lat, lon = latLon + localRef = self._calcReference(lat0, lon0, lat, lon) + localRef = localRef + " OF " + label + localRef = localRef.replace(",","") + localRefs.append(localRef) + return localRefs + + def _calcReference(self, lat0, lon0, lat1, lon1): + #return self._oldCalcReference(lat0, lon0, lat1, lon1) + distKm = self._distanceFromLatLon(lat0, lon0, lat1, lon1) + distMph = distKm * 0.62 + # Round to nearest 10 + distMph = self.round(distMph, "Nearest", 10) + distMph_str = `int((distMph/10)*10)` + #distKm_str = `int((distKm/10)*10)` + direction = self._bearing(lat1, lon1, lat0, lon0) + direction = self._dirInEnglish(direction) + localRef ="ABOUT "+distMph_str+" MILES "+direction + #print "localRef", localRef + return localRef + + # Returns the distance from lat0, lon0 to lat1, lon1 in kilometers + def _distanceFromLatLon(self, lat0, lon0, lat1, lon1): + R = 6371.0 + lat0 = lat0 * DEG_TO_RAD + lon0 = lon0 * DEG_TO_RAD + lat1 = lat1 * DEG_TO_RAD + lon1 = lon1 * DEG_TO_RAD + dist = math.acos(math.sin(lat0) * math.sin(lat1) + math.cos(lat0) * math.cos(lat1) * math.cos(lon1 - lon0)) * R + return dist + + def _bearing(self, lat0, lon0, lat1, lon1): + + dlat = (lat0 - lat1) * DEG_TO_RAD + dlon = (lon0 - lon1) * DEG_TO_RAD + + y = math.sin(dlon) * math.cos(lat1 * DEG_TO_RAD) + x = math.cos(lat0 * DEG_TO_RAD) * math.sin(lat1 * DEG_TO_RAD) - \ + (math.sin(lat0 * DEG_TO_RAD) * math.cos(lat1 * DEG_TO_RAD) * math.cos(dlon)) + + direction = (math.atan2(x, y) / DEG_TO_RAD) - 90.0 + if direction < 0.0: + direction = direction + 360.0 + direction = direction % 360 + + return direction + +## lat0 = 30.0 +## lat1 = 20.0 +## lon0 = -80.0 +## lon1 = -90.0 + +## print "complex dist:", distComplex(lat0, lon0, lat1, lon1) +## print "bearing:", bearing(lat0, lon0, lat1, lon1) + + + def _dirInEnglish(self, direction): + dirList = ["North", "North-Northeast", "Northeast", "East-Northeast", + "East", "East-Southeast", "Southeast", "South-Southeast", + "South", "South-Southwest", "Southwest", "West-Southwest", + "West", "West-Northwest", "Northwest", "North-NorthWest"] + dirIndex = int((direction + 11.25) / 22.5) + if dirIndex > 15: + dirIndex = dirIndex - 16 + return dirList[dirIndex] + + ###################################################### + # Product Part processing + ###################################################### + + def _processProductParts(self, productGenerator, productDict, productSegmentGroup, productParts): + ''' + @param productDict + @param productSegmentGroup + @param productParts + @return product dictionary created from the product parts + + Note that this method is called recursively such that a product part is allowed to be + a set of subParts specified as follows: + (subPartLabel, list of productParts for each subPart) + For example, we have + ('segments', [list of [segment product parts]]) + + # Product Dictionary + # Contains information for all formats e.g. + # partner XML, CAP, and Legacy text + ''' + + + if type(productParts) is types.DictType: + arguments = productParts.get('arguments') + partsList = productParts.get('partsList') + else: + partsList = productParts + + removedParts = [] + for part in partsList: + if type(part) is types.TupleType: + # e.g. subPart == 'segments', subPartsLists == list of parts for each segment + subPart, subPartsLists = part + subParts = [] + for subPartsList in subPartsLists: + subDict = collections.OrderedDict() + self._processProductParts(productGenerator, subDict, productSegmentGroup, subPartsList) + subParts.append(subDict) + # e.g. productDict['segments'] = segment dictionaries + productDict[subPart] = subParts + else: + if part not in self._noOpParts(): + execString = 'productGenerator._'+part+'(productDict, productSegmentGroup, arguments)' + exec execString + if part not in productDict: + removedParts.append(part) + + for part in removedParts: + print "SARAH: Removing part =", part + partsList.remove(part) + + def _noOpParts(self): + ''' + These represent product parts that should be skipped when calling product part methods. + They will be handled automatically by the formatters. + ''' + return ['CR', 'endProduct', 'endSegment', 'doubleAmpersand', 'newInformationHeader', 'sigPotentialImpacts'] + + ############################################################### + ### Product Dictionary methods + + def _createProductDictionary(self): + # Create the product dictionary + productSegmentGroup = self._groupSegments(self._allAreas()) + + productDict = self._initializeProductDict(productSegmentGroup) + productParts = productSegmentGroup.get('productParts') + productDict['productParts'] = productParts + self._processProductParts(self, productDict, productSegmentGroup, productParts) +# self._wrapUpProductDict(productDict) + + return productDict + + def _formatProductDictionary(self, productDict): + legacyFormatter = LegacyFormatter(self) + product = legacyFormatter.execute(productDict) +# xmlFormatter = XMLFormatter(self) +# product = xmlFormatter.execute(productDict) + + return product + + def _groupSegments(self, segments): + ''' + Group the segments into the products + return a list of productSegmentGroup dictionaries + ''' + + segment_vtecRecords_tuples = [] + for segment in segments: + vtecRecords = self.getVtecRecords(segment) + segment_vtecRecords_tuples.append((segment, vtecRecords)) + + productSegmentGroup = { + 'productID' : 'HLS', + 'productName': self._productName, + 'geoType': 'area', + 'vtecEngine': self._hazardsTable, + 'mapType': 'publicZones', + 'segmented': True, + 'productParts': self._productParts_HLS(segment_vtecRecords_tuples), + } + + return productSegmentGroup + + ###################################################### + # Product Dictionary -- General product information + ###################################################### + + def _initializeProductDict(self, productSegmentGroup): + ''' + Set up the Product Dictionary for the given Product consisting of a + group of segments. + + Fill in the dictionary information for the product header. + + @param productSegmentGroup: holds meta information about the product + @return initialized product dictionary + + *********** + Example segmented product: + + WGUS63 KBOU 080400 + FFABOU + + URGENT - IMMEDIATE BROADCAST REQUESTED + FLOOD WATCH + NATIONAL WEATHER SERVICE DENVER CO + 400 AM GMT TUE FEB 8 2011 + + Overview Headline + Overview + + *********** + Example non-segmented product: + WGUS63 KBOU 080400 + FFWBOU + + ''' + self._productID = productSegmentGroup.get('productID', 'NNN') + if self._areaName != '': + self._areaName = ' FOR ' + self._areaName + '\n' + self._geoType = productSegmentGroup.get('geoType') + self._mapType = productSegmentGroup.get('mapType') + self._productTimeZones = [] + + # Fill in product dictionary information + productDict = collections.OrderedDict() + productDict['productID'] = self._productID + return productDict + + ############################################################### + ### Hazards related methods + + def _initializeHazardsTable(self, argDict): + import VTECMessageType + productID = self._pil[0:3] + vtecMode = VTECMessageType.getVTECMessageType(productID) + argDict["vtecMode"] = vtecMode + + self._setVTECActiveTable(argDict) + + # Need to check hazards against all edit areas in the CWA MAOR + argDict["combinations"]= [(self._allAreas(),"Region1")] + + self._hazardsTable = self._getHazardsTable(argDict, self.filterMethod) + argDict["hazards"] = self._hazardsTable + + def _setVTECActiveTable(self, argDict): + dataMgr = argDict["dataMgr"] + gfeMode = dataMgr.getOpMode().name() + + if gfeMode == "PRACTICE": + argDict["vtecActiveTable"] = "PRACTICE" + else: + argDict["vtecActiveTable"] = "active" + + def _allAreas(self): + return self._inlandAreas() + self._coastalAreas() + + ############################################################### + ### Time related methods + + def _initializeTimeVariables(self, argDict): + argDict['creationTime'] = int(time.time()/60)*60.0 + self._issueTime_secs = argDict['creationTime'] + self._issueTime = self._issueTime_secs * 1000 # in milliseconds + + self._ddhhmmTime = self.getCurrentTime( + argDict, "%d%H%M", shiftToLocal=0, stripLeading=0) + self._currentTime = self._issueTime_secs + self._expireTime = self._issueTime_secs + self._purgeTime*3600 + self._timeLabel = self.getCurrentTime( + argDict, "%l%M %p %Z %a %b %e %Y", stripLeading=1) + + ############################################################### + ### GUI related methods + + def _overview_list(self): + return [ + { + "name": "ImpactsAnticipated", + "label": "Step 1. Potential Impacts Anticipated?", + "options": [ + ("Yes", True), + ("No (Dispel Rumors)", False), + ], + "default": "Yes", + }, + { + "name": "StormInfo", + "label": "Step 2. Obtain Storm Type/Name/Info", + "options": [ + "TCPAT1", "TCPAT2", "TCPAT3", "TCPAT4", "TCPAT5", + "Enter PIL below (e.g. TCPEP1):", + ], + "entryField": " ", + }, + { + "name":"IncludedImpacts", + "label": "Step 3. Potential Impacts to Include and Order", + "optionType": "check", + "options": [ + ("Wind", 'windSection'), + ("Surge", 'surgeSection'), + ("Flooding Rain", 'floodingRainSection'), + ("Tornadoes", 'tornadoSection'), + ("Other Coastal Hazards", 'coastalHazardsSection') + ], + "default": ["Wind", "Surge", "Flooding Rain", "Tornadoes", "Other Coastal Hazards"], + }, + { + "name":"LocalReferencePoints", + "label": "Step 4. Locate Storm Relative to Local Reference Points\n(choose at most "\ + +self._referencePointLimit()[1]+")", + "optionType": "check", + "options": self._localReferencePoints(), + "default": self._localReferencePoints_defaults(), + }, + { + "name": "MainHeadline", + "label": "Step 5. Input Main Headline (required)", + "options": [ + ("Enter Unique Headline (below)", "Enter"), + ("Use Previous HLS Headline", "UsePrev"), + ("Use Latest TCP Headline", "UseTCP"), + ], + "entryField": " ", + }, + { + "name": "NextUpdate", + "label": "Step 6. Indicate Next Update Time", + "options": [ + ("As Conditions Warrant", "Conditions"), + ("Last Issuance", "LastIssuance"), + ("Enter Approximate Time (below)", "Enter") + ], + "default": "Shortly", + "entryField": " e.g. 6 AM EDT", + }, + ] + + def _displayGUI(self, infoDict=None): + dialog = Overview_Dialog(self, "HLS", infoDict) + status = dialog.status() + LogStream.logVerbose("status="+status) + if status == "Cancel": + return None + else: + return dialog.getVarDict() + + def _frame(self, text): + return "|* " + text + " *|" + + +class Overview_Dialog(Hazard_HLSTCV_Common.Common_Dialog): + def __init__(self, parent, title, infoDict=None): + Hazard_HLSTCV_Common.Common_Dialog.__init__(self, parent, title, infoDict) + + def body(self, master): + # build the main display dialog + tkObject_dict = self._tkObject_dict + overviewList = self._parent._overview_list() + fontDict = self._parent._font_GUI_dict() + + # OVERVIEW header + headerFG, headerFont = fontDict["headers"] + frame = Tkinter.Frame(master, relief=Tkinter.GROOVE, borderwidth=1) + frame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=Tkinter.NO) + + numBoxes = 3 + + boxes = [] + for i in range(numBoxes): + newBox = Tkinter.Frame(master) + newBox.pack(side=Tkinter.TOP, expand=Tkinter.NO, + fill=Tkinter.Y, anchor=Tkinter.W) + boxes.append(newBox) + + for infoDict in overviewList: + name = infoDict["name"] + label = infoDict["label"] + options = infoDict.get("options", []) + entryField = infoDict.get("entryField", None) + default = infoDict.get("default", None) + optionType = infoDict.get("optionType", "radio") + + index = overviewList.index(infoDict) + if index in [0,1,2]: + boxNum = 0 + buttonSide=Tkinter.TOP + frameSide = Tkinter.LEFT + elif index in [3,4]: + boxNum = 1 + buttonSide=Tkinter.LEFT + frameSide=Tkinter.TOP + else: + boxNum = 2 + buttonSide=Tkinter.TOP + frameSide=Tkinter.LEFT + + box = boxes[boxNum] + + if name == "MainHeadline": entryField = None + + if name == "IncludedImpacts": + tkObject_dict[name], entryObject = self._makeStep3( + box, label, options, default, buttonSide=buttonSide, frameSide=frameSide, + entryField=entryField, headerFG=headerFG, + headerFont=headerFont) + else: + tkObject_dict[name], entryObject = self._makeRadioOrCheckList( + box, label, options, default, buttonSide=buttonSide, frameSide=frameSide, + entryField=entryField, headerFG=headerFG, + headerFont=headerFont, boxType=optionType) + if entryObject is not None: + tkObject_dict[self._entryName(name)] = entryObject + + if name == "MainHeadline": + frame = Tkinter.Frame(box, relief=Tkinter.GROOVE, borderwidth=1) + tkObject_dict[self._entryName(name)] = self._makeEntry(frame, "", 80) + frame.pack(fill=Tkinter.X, expand=Tkinter.YES) + + # Buttons + frame = Tkinter.Frame(master, relief=Tkinter.GROOVE, borderwidth=1) + self._makeButtons(frame) + frame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=Tkinter.NO) + + def _makeStep3(self, master, label, elementList, default=None, + buttonSide=Tkinter.TOP, frameSide=Tkinter.LEFT, entryField=None, + headerFG=None, headerFont=None, + listFrameRelief=Tkinter.GROOVE): + listFrame = Tkinter.Frame(master, relief=listFrameRelief, borderwidth=1) + + if label != "": + listLabel = Tkinter.Label(listFrame, text=label, fg=headerFG, font=headerFont) + listLabel.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=Tkinter.NO, padx=10) + + ivar = Tkinter.IntVar() + ivarList = [] + for element in elementList: + index = elementList.index(element) + if type(element) is types.TupleType: + element, key = element + + ivar = Tkinter.IntVar() + if default is not None and element in default: ivar.set(1) + else: ivar.set(0) + + buttonFrame = Tkinter.Frame(listFrame) + + button= Tkinter.Checkbutton(buttonFrame, variable=ivar, text=element) + button.grid(row=0, column=0, sticky=Tkinter.W+Tkinter.E) + button.grid_columnconfigure(0, weight=1) + ivarList.append(ivar) + + entry = Tkinter.Entry(buttonFrame, relief=Tkinter.SUNKEN, width=3) + entry.grid(row=0, column=1, sticky=Tkinter.E) + + buttonFrame.pack(side=buttonSide, fill=Tkinter.X, expand=Tkinter.YES, padx=4) + + entryObject = None + if entryField is not None: + entryObject = self._makeEntry(listFrame, entryField) + + noteLabel = Tkinter.Label(listFrame, text="Note: Check Hazards to include (left) and order number (right)") + noteLabel.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=Tkinter.NO, padx=10) + + # packing + listFrame.pack(side=frameSide, expand=Tkinter.NO, fill=Tkinter.Y) #, anchor=Tkinter.N) + + ivar = ivarList + return ivar, entryObject + + def _makeButtons(self, master): + frame = Tkinter.Frame(master) + buttonList = self._parent._GUI1_configDict().get("buttonList", []) + for button, label in buttonList: + if button == "Next": + command = self.okCB + else: # Cancel + command = self.cancelCB + Tkinter.Button(frame, text=label, command=command, width=10, + state=Tkinter.NORMAL).pack(side=Tkinter.LEFT, pady=5, padx=10) + frame.pack() + + def okCB(self): + # pull the data from the tkObject_dict before they get toasted + tkObject_dict = self._tkObject_dict + overviewList = self._parent._overview_list() + for infoDict in overviewList: + name = infoDict["name"] + label = infoDict["label"] + options = infoDict.get("options", []) + entryField = infoDict.get("entryField", None) + default = infoDict.get("default", None) + optionType = infoDict.get("optionType", "radio") + + if optionType == "check": + checkList = [] + ivarList = tkObject_dict[name] + for i in range(len(options)): + if ivarList[i].get(): + checkList.append(options[i]) + value = checkList + self._setVarDict(name, value) + else: + value = tkObject_dict[name].get() + self._setVarDict(name, value, options) + + if entryField is not None: + entryName = self._entryName(name) + self._setVarDict(entryName, tkObject_dict[entryName].get()) + # close window and set status "Ok" + self._status = "Ok" + self.withdraw() + self.ok() + + +class LegacyFormatter(): + def __init__(self, textProduct): + self._textProduct = textProduct + self.TAB = " "*self._textProduct._tabLength + self._tpc = Hazard_HLSTCV_Common.TextProductCommon() + + def execute(self, productDict): + self.productDict = productDict + productParts = self._tpc.getVal(productDict, 'productParts', []) + text = self._processProductParts(productDict, productParts.get('partsList')) + return text + + def _processProductParts(self, productDict, productParts, skipParts=[]): + ''' + Adds the product parts to the product + @param productDict -- dictionary of information -- could be the product dictionary or a sub-part such as a segment + @param skipParts -- necessary to avoid repetition when calling this method recursively + @param productParts -- list of instances of the ProductPart class with information about how to format each product part + @return text -- product string + ''' + text = '' + print "SARAH: productParts =", productParts + for part in productParts: + valtype = type(part) + if valtype is str: + name = part + elif valtype is tuple: + name = part[0] + infoDicts = part[1] + print "SARAH: name =", str(name) + print "SARAH: infoDicts =", infoDicts + newtext = self.processSubParts(productDict.get(name), infoDicts) + print "SARAH: newtext type =", type(newtext) + print "SARAH: newtext =", repr(newtext) + text += newtext + continue + elif valtype is list: + print 'GOT HERE -- found list' + self._tpc.flush() + # TODO THIS SHOULD BE REMOVED AFTER THE REFACTOR OF HazardServicesProductGenerationHandler.JAVA + tup = (part[0], part[1]) + part = tup + name = part[0] + + + if name == 'wmoHeader': + text += self.processWmoHeader(productDict['wmoHeader']) + elif name == 'ugcHeader': + text += productDict['ugcHeader'] + "\n\n" + elif name == 'productHeader': + text += self.processProductHeader(productDict['productHeader']) + elif name == 'vtecRecords': + for vtecString in productDict['vtecRecords']: + text += vtecString + '\n' + elif name == 'areaList': + text += productDict['areaList'] + "\n\n" + elif name == 'issuanceTimeDate': + text += productDict['issuanceTimeDate'] + '\n\n' + elif name == 'summaryHeadlines': + text += self.processSummaryHeadlines(productDict['summaryHeadlines']) + elif name == "newInformationHeader": + header = "New Information" + text += header + "\n" + "-"*len(header) + "\n\n" + elif name == "changesHazards": + text += "* Changes to Watches and Warnings:\n" + self.processHazards(productDict['changesHazards']) + elif name == "currentHazards": + text += "* Current Watches and Warnings:\n" + self.processHazards(productDict['currentHazards']) + elif name == "stormInformation": + text += self.processStormInformation(productDict['stormInformation']) + elif name == "situationOverview": + text += self.processSituationOverview(productDict['situationOverview']) + elif name == "sigPotentialImpacts": + header = "Significant Potential Impacts" + text += header + "\n" + "-"*len(header) + "\n\n" + if not self._textProduct._ImpactsAnticipated: + text += "None\n\n" + elif name in ['windSection', 'surgeSection', 'floodingRainSection', 'tornadoSection']: + text += self.processHazardsSection(productDict[name]) + elif name == "coastalHazardsSection": + text += "* Other Coastal Hazards:\n" + text += self._textProduct.indentText(productDict[name], maxWidth=self._textProduct._lineLength) + "\n" + elif name == "preparednessSection": + header = productDict[name]['title'] + text += header + "\n" + "-"*len(header) + "\n\n" + if productDict[name]['genericAction'] is not None: + text += self._textProduct.indentText(productDict[name]['genericAction'], maxWidth=self._textProduct._lineLength) + "\n" + elif name == "evacuationStatements": + text += "* " + productDict[name]['title'] + ":\n" + for statement in productDict[name]['statements']: + text += self._textProduct.indentText(statement, maxWidth=self._textProduct._lineLength) + "\n" + elif name == "otherPreparednessActions": + text += "* " + productDict[name]['title'] + ":\n" + for action in productDict[name]['actions']: + text += self._textProduct.indentText(action, maxWidth=self._textProduct._lineLength) + "\n" + elif name == "additionalSourcesInfo": + text += "* " + productDict[name]['title'] + ":\n" + for source in productDict[name]['sources']: + text += self._textProduct.indentText(source, maxWidth=self._textProduct._lineLength) + text += "\n" + elif name == "nextUpdate": + header = "Next Update" + text += header + "\n" + "-"*len(header) + "\n\n" + text += self._textProduct.indentText(productDict[name], maxWidth=self._textProduct._lineLength) + "\n" + elif 'sectionHeader' in name: + text += "* " + productDict[name] + "\n" + elif 'Subsection' in name: + text += self.processSubsection(productDict[name]) + elif name == 'infoSection': + text += self.processInfoSection(productDict['infoSection']) + elif name == 'endProduct': + text += '$$\n' + elif name == 'CR': + text += '\n' + elif name == 'doubleAmpersand': + text += '&&\n' + elif name not in self._noOpParts(): + textStr = productDict.get(name) + print "SARAH: name =", name + print "SARAH: textStr =", textStr + if textStr: + text += textStr + '\n' + return text + + def _noOpParts(self): + ''' + These represent product parts that should be skipped when calling product part methods. + They will be handled automatically by the formatters. + ''' + return ["setup_segment"] #['CR', 'endProduct', 'endSegment', 'issuanceDateTime', 'doubleAmpersand'] + + def processWmoHeader(self, wmoHeader): + text = wmoHeader['TTAAii'] + ' ' + wmoHeader['fullStationID'] + ' ' + wmoHeader['ddhhmmTime'] + '\n' + text += wmoHeader['productID'] + wmoHeader['siteID'] + '\n' + return text + + def processProductHeader(self, headerDict): + if not self._textProduct._ImpactsAnticipated: + text = "Tropical Local Statement\n" + else: + text = headerDict['stormType'] + ' ' + headerDict['stormName'] + ' ' + headerDict['productName'] + + advisoryText = '' + if headerDict['advisoryType'] is not None and \ + headerDict['advisoryType'].lower() in ["intermediate", "special"]: + advisoryText = headerDict['advisoryType'] + ' ' + + if headerDict['advisoryNumber'] is not None: + advisoryText += 'Advisory Number ' + headerDict['advisoryNumber'] + + if len(advisoryText) > 0: + if len(text + " " + advisoryText) > self._textProduct._lineLength: + text += '\n' + else: + text += ' ' + + text += advisoryText + '\n' + else: + text += '\n' + + text += "National Weather Service " + headerDict['cityState'] + " " + headerDict['stormNumber'] + '\n' + text += headerDict['issuanceTimeDate'] + '\n\n' + + return text + + def formatIssueTime(self): + text = '' + sentTimeZ = self._tpc.getVal(self.productDict, 'sentTimeZ_datetime') + timeZones = self._tpc.getVal(self.productDict, 'timeZones') + for timeZone in timeZones: + text += self._tpc.formatDatetime(sentTimeZ, '%I%M %p %Z %a %e %b %Y', timeZone) + '\n' + return text + + def processSummaryHeadlines(self, headlinesList): + if headlinesList in [[], [""]]: + text = "**" + self._textProduct._frame("Enter headline here") + "**\n\n" + else: + text = "" + for headline in headlinesList: + text += self._textProduct.indentText("**" + headline + "**\n", + maxWidth=self._textProduct._lineLength) + + text = self._textProduct._frame(text) + "\n" + + return text + + def processHazards(self, hazardsList): + text = "" + + if len(hazardsList) == 0: + text = self.TAB + "- None\n" + for hazard in hazardsList: + hazardText = "" + if hazard['act'] == "CON": + hazardText = "A " + hazard['hdln'] + " remains in effect for:\n" + elif hazard['act'] in ["NEW", "EXA"]: + hazardText = "A " + hazard['hdln'] + " has been issued for:\n" + elif hazard['act'] == "UPG": + hazardText = "A " + hazard['hdln'] + " has been upgraded for:\n" + elif hazard['act'] == "CAN": + hazardText = "The " + hazard['hdln'] + " has been cancelled for:\n" + text += self._textProduct.indentText(hazardText, + indentFirstString = self.TAB + "- ", + indentNextString = self.TAB + " ", + maxWidth=self._textProduct._lineLength) + text += self._textProduct.indentText(self._areaWords(hazard['id']) + "\n", + indentFirstString = self.TAB + " ", + indentNextString = self.TAB + " ", + maxWidth=self._textProduct._lineLength) + text += "\n" + + return text + + def _areaWords(self, areas): + if areas == []: + return "" + names = [] + areaDict = self._textProduct._areaDict + areas.sort() + for area in areas: + name = areaDict[area].get('altName', areaDict[area].get('ugcName', '')) + names.append(name) + areaWords = self._textProduct.formatCountyString("", names)[1:] + return areaWords + + def processStormInformation(self, stormInfoDict): + text = "* Storm Information:\n" + + if len(stormInfoDict) == 0: + text += self.TAB + "- None\n\n" + else: + referenceText = "" + for reference in stormInfoDict['references']: + referenceText += reference + " or " + referenceText = referenceText[:-4] + "\n" # remove the last " or " + + text += self._textProduct.indentText(referenceText, + indentFirstString = self.TAB + "- ", + indentNextString = self.TAB + " ", + maxWidth=self._textProduct._lineLength) + + (lat, lon) = stormInfoDict['location'] + text += self.TAB + "- " + lat + " " + lon + "\n" + + text += self.TAB + "- " + stormInfoDict['intensity'] + "\n" + + text += self.TAB + "- " + stormInfoDict['movement'] + "\n\n" + + return text + + def processSituationOverview(self, overviewText): + title = "Situation Overview" + text = title + "\n" + "-"*len(title) + "\n\n" + + text += self._textProduct.indentText(self._textProduct._frame(overviewText), + maxWidth=self._textProduct._lineLength) + text += "\n" + + return text + + def processHazardsSection(self, sectionDict): + text = "* " + sectionDict['title'] + ":\n" + + impactRangeText = sectionDict['impactRange'] + if sectionDict['variedImpacts'] is not None: + if sectionDict['variedImpacts']: + impactRangeText += " In these areas, potential impacts include:" + else: + impactRangeText += " Potential impacts include:" + + text += self._textProduct.indentText(impactRangeText, maxWidth=self._textProduct._lineLength) + + for impact in sectionDict['impactLib']: + text += self._textProduct.indentText(impact, + indentFirstString = self.TAB + "- ", + indentNextString = self.TAB + " ", + maxWidth=self._textProduct._lineLength) + + if len(sectionDict['additionalImpactRange']) != 0: + text += "\n" + + additionalImpactRangeText = "" + for additionalImpact in sectionDict['additionalImpactRange']: + additionalImpactRangeText += additionalImpact + " " + + # Remove the trailing space + additionalImpactRangeText = additionalImpactRangeText[:-1] + + text += self._textProduct.indentText(additionalImpactRangeText, maxWidth=self._textProduct._lineLength) + + text += "\n" + return text + + def processSubParts(self, subParts, infoDicts): + """ + Generates Legacy text from a list of subParts e.g. segments or sections + @param subParts: a list of dictionaries for each subPart + @param partsLists: a list of Product Parts for each segment + @return: Returns the legacy text of the subParts + """ + text = '' + for i in range(len(subParts)): + print "SARAH: subpart subParts[i] =", subParts[i] + print "SARAH: subpart infoDicts[i] =", infoDicts[i] + newtext = self._processProductParts(subParts[i], infoDicts[i].get('partsList')) + print "SARAH: subpart newtext type =", type(newtext) + print "SARAH: subpart newtext =", repr(newtext) + text += newtext + return text + diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py new file mode 100644 index 0000000000..541ff42009 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py @@ -0,0 +1,2050 @@ +import GenericHazards +import string, time, os, types, copy, LogStream, collections +import ModuleAccessor, SampleAnalysis, EditAreaUtils +import math + + +from AbsTime import * +from StartupDialog import IFPDialog as Dialog +from LockingFile import File +from com.raytheon.uf.common.localization import PathManagerFactory +from com.raytheon.uf.common.localization import LocalizationContext_LocalizationType as LocalizationType, LocalizationContext_LocalizationLevel as LocalizationLevel +AWIPS_ENVIRON = "AWIPS2" + +import Hazard_HLSTCV_Common + +class TextProduct(Hazard_HLSTCV_Common.TextProduct): + Definition = copy.deepcopy(GenericHazards.TextProduct.Definition) + + Definition["displayName"] = "None" + Definition["outputFile"] = "{prddir}/TEXT/TCV.txt" + Definition["database"] = "Official" # Source database + Definition["debug"] = 1 + Definition["mapNameForCombinations"] = "Zones_" + Definition["defaultEditAreas"] = "EditAreas_PublicMarine_" + Definition["showZoneCombiner"] = 1 # 1 to cause zone combiner to display + + Definition["productName"] = "LOCAL WATCH/WARNING STATEMENT" + + Definition["fullStationID" ] = "" + Definition["wmoID" ] = "" + Definition["wfoCityState" ] = "" + Definition["pil" ] = "" + Definition["textdbPil" ] = "" + Definition["awipsWANPil" ] = "" + Definition["site"] = "" + Definition["wfoCity"] = "" + + Definition["areaName"] = "" #optional area name for product + Definition["areaDictionary"] = "AreaDictionary" + Definition["language"] = "english" + Definition["lineLength"] = 71 #Maximum line length + Definition["tabLength"] = 4 + + Definition["purgeTime"] = 8 # Default Expiration in hours if + Definition["includeZoneNames"] = 1 # Zone names will be included in the area header + Definition["includeIssueTime"] = 0 # Issue Time will be included in the area header + Definition["easPhrase"] = \ + "URGENT - IMMEDIATE BROADCAST REQUESTED" # Optional EAS phrase to be include in product header + Definition["callToAction"] = 1 + + def __init__(self): + Hazard_HLSTCV_Common.TextProduct.__init__(self) + + ##################################################################### + ##################################################################### + ### Organization of Formatter Code + + ############################################################### + ### MUST OVERRIDE DEFINITIONS !!! + ### _inlandAreas, _coastalAreas, _cwa + ############################################################### + + ############################################################### + ### Analysis Lists, SampleAnalysis Overrides and other + ### analysis related methods + ############################################################### + + ############################################################### + ### Hazards and Additional Hazards + ### allowedHazards is used for VTEC records and summary + ### headlines + ### allowedHeadlines are additional hazards reported in + ### certain sections + ############################################################### + + ############################################################### + ### TCV Product and Segment Parts Definition + ############################################################### + + ############################################################### + # CODE + ############################################################### + ### High level flow of formatter + ### generateForecast, initializeVariables, + ### determineSegments, determineTimeRanges, sampleData, + ### createProductDictionary, formatProductDictionary, + ### archiveCurrentAdvisory... + ############################################################### + + ############################################################### + ### Product Dictionary methods + ############################################################### + + ############################################################### + ### Area, Zone and Segment related methods + ############################################################### + + ############################################################### + ### Hazards related methods + ############################################################### + + ############################################################### + ### Sampling and Statistics related methods + ############################################################### + + ############################################################### + ### Time related methods + ############################################################### + + ############################################################### + ### Storm Information and TCP related methods + ############################################################### + + ############################################################### + ### Advisory related methods + ############################################################### + + ############################################################### + ### GUI related methods + ############################################################### + + + ############################################################### + ### MUST OVERRIDE DEFINITIONS !!! + + def _inlandAreas(self): + return [ + #"FLZ052", "FLZ056", "FLZ057", "FLZ061", "FLZ043", + ] + + def _coastalAreas(self): + return [ + #"FLZ039", "FLZ042", "FLZ048", "FLZ049", "FLZ050", "FLZ051", "FLZ055", "FLZ060", + #"FLZ062", + ] + + def _cwa(self): + return "" #"MFL" + + + ############################################################### + ### TCV Product and Segment Parts Definition + + def _productParts_TCV(self, segment_vtecRecords_tuples): + segmentParts = [] + for segment_vtecRecords_tuple in segment_vtecRecords_tuples: + segmentParts.append(self._segmentParts_TCV(segment_vtecRecords_tuple)) + return { + 'partsList': [ + 'wmoHeader', + 'easMessage', + 'productHeader', + ('segments', segmentParts), + 'endProduct', + ] + } + + def _segmentParts_TCV(self, segment_vtecRecords_tuple): + segment, _ = segment_vtecRecords_tuple + + windSection = 'windSection[\'' + segment + '\']' + stormSurgeSection = 'stormSurgeSection[\'' + segment + '\']' + floodingRainSection = 'floodingRainSection[\'' + segment + '\']' + tornadoSection = 'tornadoSection[\'' + segment + '\']' + + partsList = [ + 'setup_segment', + 'ugcHeader', + 'vtecRecords', + 'areaList', + 'issuanceTimeDate', + 'summaryHeadlines', + 'locationsAffected', + 'fcstConfidence', + (windSection, self._windSection[segment].sectionParts(segment_vtecRecords_tuple)), + (floodingRainSection, self._floodingRainSection[segment].sectionParts(segment_vtecRecords_tuple)), + (tornadoSection, self._tornadoSection[segment].sectionParts(segment_vtecRecords_tuple)), + 'infoSection', + ] + + # The storm surge section should never be inserted into + # "inland" zones, since there will never be a surge impact. + if segment not in self._inlandAreas(): + partsList.insert(9, + (stormSurgeSection, self._stormSurgeSection[segment].sectionParts(segment_vtecRecords_tuple))) + + return { + 'arguments': segment_vtecRecords_tuple, + 'partsList': partsList + } + + ############################################################### + ### High level flow of formatter + + def generateForecast(self, argDict): + # Generate Text Phrases for a list of edit areas + + error = self._initializeVariables(argDict) + if error is not None: + return error + + self._segmentList = self._determineSegments() + print "Segment Information: ", self._segmentList, "\n\n" + if len(self._segmentList) == 0: + return "NO HAZARDS TO REPORT" + + # Determine time ranges + self._determineTimeRanges(argDict) + + # Sample the data + self._sampleTCVData(argDict) + + # Create the product dictionary and format it to create the output + productDict = self._createProductDictionary() + productOutput = self._formatProductDictionary(productDict) + + self._archiveCurrentAdvisory() + + return productOutput + + def _initializeVariables(self, argDict): + # Get variables + error = self._getVariables(argDict) + if error is not None: + return error + + self._backupFullStationID = self._fullStationID + self._argDict = argDict + + argDict["definition"] = self._definition + + self._initializeTimeVariables(argDict) + + self._initializeHazardsTable(argDict) + + error = self._initializeStormInformation() + if error is not None: + return error + + if self._stormName is None or self._stormName.strip() == "": + return "Could not determine the storm name" + + self._windSection = dict() + self._stormSurgeSection = dict() + self._floodingRainSection = dict() + self._tornadoSection = dict() + + self._initializeAdvisories() + + # Set up the areaDictionary for all to use + accessor = ModuleAccessor.ModuleAccessor() + self._areaDict = accessor.variable(self._areaDictionary, "AreaDictionary") + self._tpc = Hazard_HLSTCV_Common.TextProductCommon() + self._tpc.setUp(self._areaDict) + + return None + + ############################################################### + ### Product Dictionary methods + + def _createProductDictionary(self): + # Create the product dictionary + productSegmentGroup = self._groupSegments(self._segmentList) + + productDict = self._initializeProductDict(productSegmentGroup) + productParts = productSegmentGroup.get('productParts') + productDict['productParts'] = productParts + self._processProductParts(self, productDict, productSegmentGroup, productParts) + self._wrapUpProductDict(productDict) + + return productDict + + def _formatProductDictionary(self, productDict): + legacyFormatter = LegacyFormatter(self) + product = legacyFormatter.execute(productDict) +# xmlFormatter = XMLFormatter(self) +# product = xmlFormatter.execute(productDict) + + return product + + ###################################################### + # Populate Product Parts for HLS and TCV + ###################################################### + + ################# Product Level + + def _easMessage(self, productDict, productSegmentGroup, arguments=None): + productDict['easMessage'] = self._easPhrase + + ################# Segment Level + + def _setup_segment(self, segmentDict, productSegmentGroup, productSegment): + segment, vtecRecords = productSegment + print 'setup_segment productSegment', productSegment + # NOTE -- using getVtecRecords to change to milliseconds + self._segmentVtecRecords = self.getVtecRecords(segment) + + # UGCs and Expire Time + # Assume that the geoType is the same for all hazard events in the segment i.e. area or point + self._ugcs = [segment] + self._timeZones = self._tpc.hazardTimeZones(self._ugcs) + segmentDict['timeZones'] = self._timeZones + + for tz in self._timeZones: + if tz not in self._productTimeZones: + self._productTimeZones.append(tz) + self._purgeHours = self._purgeTime + self._expireTime = self._tpc.getExpireTime( + self._issueTime, self._purgeHours, self._segmentVtecRecords) + segmentDict['expireTime'] = self._convertToISO(self._expireTime) + + # CAP Specific Fields + segmentDict['status'] = 'Actual' + self._summaryHeadlines_value, self._headlines = self._tpc.getHeadlinesAndSections( + self._segmentVtecRecords, self._productID, self._issueTime_secs) + + def _ugcHeader(self, segmentDict, productSegmentGroup, productSegment): + segmentDict['ugcCodes'] = self._formatUGC_entries() + self._ugcHeader_value = self._tpc.formatUGCs(self._ugcs, self._expireTime) + segmentDict['ugcHeader'] = self._ugcHeader_value + + def _vtecRecords(self, segmentDict, productSegmentGroup, productSegment): + segment, vtecRecords = productSegment + records = [] + for vtecRecord in vtecRecords: + print "SARAH: vtecRecord dict:", vtecRecord + vstr = None + vstr = vtecRecord["vtecstr"] + + if vtecRecord["phen"] == "SS": + # SARAH: Temporary? Change the vtec mode for SS hazards to be experimental + vstr = vstr[0] + 'X' + vstr[2:] + records.append(vstr) + segmentDict['vtecRecords'] = records + + def _areaList(self, segmentDict, productSegmentGroup, productSegment): + # Area String + segmentDict['areaList'] = self._tpc.formatUGC_names(self._ugcs) + + def _locationsAffected(self, segmentDict, productSegmentGroup, productSegment): + segment, vtecRecords = productSegment + import TCVAreaDictionary + tcv_AreaDictionary = TCVAreaDictionary.TCV_AreaDictionary + + segmentDict['locationsAffected'] = [] + if segment in tcv_AreaDictionary: + segmentDict['locationsAffected'] += tcv_AreaDictionary[segment]["locationsAffected"] + + def _fcstConfidence(self, segmentDict, productSegmentGroup, productSegment): + # SARAH: TODO - Get this from the TCM product potentially? Not included until provided from NHC + return "" + + def _infoSection(self, segmentDict, productSegmentGroup, productSegment): + segment, vtecRecords = productSegment + import TCVAreaDictionary + tcv_AreaDictionary = TCVAreaDictionary.TCV_AreaDictionary + + segment, vtecRecords = productSegment + infoSection = [] + if segment in tcv_AreaDictionary: + infoSection = tcv_AreaDictionary[segment]["infoSection"] + + segmentDict['infoSection'] = infoSection + + def _issuanceTimeDate(self, segmentDict, productSegmentGroup, productSegment): + segmentDict['issuanceTimeDate'] = self._timeLabel + + def _summaryHeadlines(self, segmentDict, productSegmentGroup, productSegment): + segment, vtecRecords = productSegment + numRecords = len(vtecRecords) + definitions = [] + hazardsFound = [] + + for (phenSig, actions, name) in self.allowedHazards(): + for i in range(numRecords): + vtecRecord = vtecRecords[i] + # The 'phensig' in the VTEC record could contain an + # ETN. As such, we need to strip the ETN before doing a + # comparison with the allowedHazards. + if vtecRecord["phensig"].split(":")[0] == phenSig and \ + phenSig not in hazardsFound and \ + vtecRecord["act"] in ["NEW", "EXA"]: + + hazardsFound.append(phenSig) + definition = self._hazardDefinition(phenSig) + if definition != "": + definitions.append(definition) + + summaryDict = collections.OrderedDict() + headlines = self._summaryHeadlines_value.split("\n") + headlinesInEffect = [] + for headline in headlines: + if len(headline) != 0: + headlinesInEffect.append(headline) + summaryDict['headlinesInEffect'] = headlinesInEffect + summaryDict['headlineDefinitions'] = definitions + segmentDict['summaryHeadlines'] = summaryDict + + ###################################################### + # Product Part processing + ###################################################### + + def _processProductParts(self, productGenerator, productDict, productSegmentGroup, productParts): + ''' + @param productDict + @param productSegmentGroup + @param productParts + @return product dictionary created from the product parts + + Note that this method is called recursively such that a product part is allowed to be + a set of subParts specified as follows: + (subPartLabel, list of productParts for each subPart) + For example, we have + ('segments', [list of [segment product parts]]) + + # Product Dictionary + # Contains information for all formats e.g. + # partner XML, CAP, and Legacy text + ''' + + + if type(productParts) is types.DictType: + arguments = productParts.get('arguments') + partsList = productParts.get('partsList') + else: + partsList = productParts + + removedParts = [] + for part in partsList: + if type(part) is types.TupleType: + # e.g. subPart == 'segments', subPartsLists == list of parts for each segment + subPart, subPartsLists = part + subParts = [] + for subPartsList in subPartsLists: + subDict = collections.OrderedDict() + self._processProductParts(productGenerator, subDict, productSegmentGroup, subPartsList) + subParts.append(subDict) + # e.g. productDict['segments'] = segment dictionaries + productDict[subPart] = subParts + else: + if part not in self._noOpParts(): + execString = 'productGenerator._'+part+'(productDict, productSegmentGroup, arguments)' + exec execString + if part not in productDict: + removedParts.append(part) + + for part in removedParts: + print "SARAH: Removing part =", part + partsList.remove(part) + + def _noOpParts(self): + ''' + These represent product parts that should be skipped when calling product part methods. + They will be handled automatically by the formatters. + ''' + return ['CR', 'endProduct', 'endSegment', 'doubleAmpersand'] + + ###################################################### + # Product Dictionary -- General product information + ###################################################### + + def _initializeProductDict(self, productSegmentGroup): + ''' + Set up the Product Dictionary for the given Product consisting of a + group of segments. + + Fill in the dictionary information for the product header. + + @param productSegmentGroup: holds meta information about the product + @return initialized product dictionary + + *********** + Example segmented product: + + WGUS63 KBOU 080400 + FFABOU + + URGENT - IMMEDIATE BROADCAST REQUESTED + FLOOD WATCH + NATIONAL WEATHER SERVICE DENVER CO + 400 AM GMT TUE FEB 8 2011 + + Overview Headline + Overview + + *********** + Example non-segmented product: + WGUS63 KBOU 080400 + FFWBOU + + ''' + self._productID = productSegmentGroup.get('productID', 'NNN') + if self._areaName != '': + self._areaName = ' FOR ' + self._areaName + '\n' + self._geoType = productSegmentGroup.get('geoType') + self._mapType = productSegmentGroup.get('mapType') + self._productTimeZones = [] + + # Fill in product dictionary information + productDict = collections.OrderedDict() + productDict['productID'] = self._productID + return productDict + + def _wrapUpProductDict(self, productDict): + productDict['sentTimeZ'] = self._convertToISO(self._issueTime) + productDict['sentTimeZ_datetime'] = self._convertToDatetime(self._issueTime) + productDict['sentTimeLocal'] = self._convertToISO(self._issueTime, local=True) + productDict['timeZones'] = self._productTimeZones + return productDict + + ############################################################### + ### Area, Zone and Segment related methods + + def _groupSegments(self, segments): + ''' + Group the segments into the products + return a list of productSegmentGroup dictionaries + ''' + + segment_vtecRecords_tuples = [] + for segment in segments: + vtecRecords = self.getVtecRecords(segment) + segment_vtecRecords_tuples.append((segment, vtecRecords)) + + self._initializeSegmentZoneData(segment) + + windStats, stormSurgeStats, floodingRainStats, tornadoStats = \ + self._getTCVStats(self._argDict, segment, self._editAreaDict, self._timeRangeList) + + self._windSection[segment] = WindSection(self, segment, windStats) + self._stormSurgeSection[segment] = StormSurgeSection(self, segment, stormSurgeStats) + self._floodingRainSection[segment] = FloodingRainSection(self, segment, floodingRainStats) + self._tornadoSection[segment] = TornadoSection(self, segment, tornadoStats) + + productSegmentGroup = { + 'productID' : 'TCV', + 'productName': self._productName, + 'geoType': 'area', + 'vtecEngine': self._hazardsTable, + 'mapType': 'publicZones', + 'segmented': True, + 'productParts': self._productParts_TCV(segment_vtecRecords_tuples), + } + + return productSegmentGroup + + def _formatUGC_entries(self): + ugcCodeList = [] + for ugc in self._ugcs: + areaDictEntry = self._areaDict.get(ugc) + if areaDictEntry is None: + # We are not localized correctly for the hazard + # So get the first dictionary entry + self.logger.info('Not Localized for the hazard area -- ugc' + ugc) + keys = self._areaDict.keys() + areaDictEntry = self._areaDict.get(keys[0]) + ugcEntry = collections.OrderedDict() + ugcEntry['state'] = areaDictEntry.get('stateAbbr') + ugcEntry['type'] = self._getUgcInfo(ugc, 'type') + ugcEntry['number'] = self._getUgcInfo(ugc, 'number') + ugcEntry['text'] = ugc + ugcEntry['subArea'] = '' + ugcCodeList.append(ugcEntry) + return ugcCodeList + + def _getUgcInfo(self, ugc, part='type'): + if part == 'type': + if ugc[2] == 'C': + return 'County' + else: + return 'Zone' + if part == 'number': + return ugc[3:] + + ############################################################### + ### Hazards related methods + + def _hazardDefinition(self, phenSig): + if phenSig == "HU.W": + return "A HURRICANE WARNING MEANS HURRICANE WIND CONDITIONS ARE " + \ + "EXPECTED SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 36 HOURS" + elif phenSig == "HU.A": + return "A HURRICANE WATCH MEANS HURRICANE WIND CONDITIONS ARE " + \ + "POSSIBLE SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 48 HOURS" + if phenSig == "TR.W": + return "A TROPICAL STORM WARNING MEANS TROPICAL STORM WIND CONDITIONS ARE " + \ + "EXPECTED SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 36 HOURS" + elif phenSig == "TR.A": + return "A TROPICAL STORM WATCH MEANS TROPICAL STORM WIND CONDITIONS ARE " + \ + "POSSIBLE SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 48 HOURS" + elif phenSig == "SS.W": + return "A STORM SURGE WARNING MEANS LIFE THREATENING INUNDATION LEVELS ARE " + \ + "EXPECTED SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 36 HOURS" + elif phenSig == "SS.A": + return "A STORM SURGE WATCH MEANS LIFE THREATENING INUNDATION LEVELS ARE " + \ + "POSSIBLE SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 48 HOURS" + else: + return "" + + ############################################################### + ### Time related methods + + def _formatPeriod(self, period, wholePeriod=False, shiftToLocal=True, useEndTime=False, + resolution=3): + # Format period (a timeRange) resulting in + # DAY + MORNING / AFTERNOON / EVENING / OVERNIGHT. + # If wholePeriod, format FROM ... TO... + + print "\nMATT Format period wholePeriod = %s, period = %s, useEndTime =%s" % (str(wholePeriod), str(period), str(useEndTime)) + if period is None: return "" + if useEndTime: + startTime = period.endTime() + else: + startTime = period.startTime() + result = self._getTimeDesc(startTime, resolution, shiftToLocal) + print "MATT result = '%s'" % (result) + if wholePeriod: + endResult = self._getTimeDesc(period.endTime(), resolution, shiftToLocal) + print "MATT endResult = '%s'" % (endResult) + if result != endResult: + result=result + " TO "+ endResult + return result + + def _getTimeDesc(self, startTime, resolution=3, shiftToLocal=True): + # Create phrase such as Tuesday morning + # Handle today/tonight and "this" morning/afternoon/etc.. + # + print "\n\n**************Formatting Period for GMT starttime ", startTime + labels = self.Labels()["SimpleWorded"] + currentTime = self._timeRange.startTime() + print " currentTime", currentTime + if shiftToLocal: + currentLocalTime, shift = self.determineTimeShift() + startTime = startTime + shift + currentTime = currentTime + shift + print " shift, shifted start, current", shift/3600, startTime, currentTime + hour = startTime.hour + prevDay = False + prevDay, partOfDay = self._getPartOfDay(hour, resolution) +# if prevDay: +# startTime = startTime - 24*3600 + todayFlag = currentTime.day == startTime.day + if todayFlag: + if partOfDay.upper().find("MIDNIGHT")>0: todayWord = "tonight" + else: todayWord = "THIS" + weekday = todayWord + else: + weekday = labels["Weekday"][startTime.weekday()] + if partOfDay.find("") >= 0: + result = partOfDay.replace('', weekday) + else: + result = weekday + " " + partOfDay + print "Result", result + return result + + def _getPartOfDay(self, hour, resolution): + prevDay = False + if resolution == 3: + if hour < 3: + prevDay = True + partOfDay = "early morning" +# partOfDay = "AFTER MIDNIGHT" + elif hour < 6: + partOfDay = "early morning" + elif hour < 9: + partOfDay = "morning" + elif hour < 12: + partOfDay = "late morning" + elif hour < 15: + partOfDay = "early afternoon" + elif hour < 18: + partOfDay = "late afternoon" + elif hour < 21: + partOfDay = "early evening" + else: + partOfDay = "late evening" + else: + if hour < 6: + prevDay = True +# partOfDay = "AFTER MIDNIGHT" + partOfDay = "early morning" + elif hour < 12: partOfDay = "morning" + elif hour < 18: partOfDay = "afternoon" + else: partOfDay = "evening" + return prevDay, partOfDay + + def _convertToISO(self, time_ms, local=None): + import datetime + dt = datetime.datetime.fromtimestamp(time_ms / 1000) + if local: + timeZone = self._timeZones[0] + else: + timeZone = None + return self._tpc.formatDatetime(dt, timeZone=timeZone) + + def _convertToDatetime(self, time_ms): + import datetime + return datetime.datetime.fromtimestamp(time_ms / 1000) + + ############################################################### + ### GUI related methods + + def _overview_list(self): + return [ + { + "name": "StormInfo", + "label": "Obtain Storm Type/Name/Info", + "options": [ + "TCPAT1", "TCPAT2", "TCPAT3", "TCPAT4", "TCPAT5", + "Enter PIL below (e.g. TCPEP1):", + ], + "entryField": " ", + }, + { + "name": "PopulateSurge", + "label": "Populate Surge Section", + "options": [ + ("Populate", True), + ("Do not populate", False), + ], + "default": "None", + }, + ] + + def _displayGUI(self, infoDict=None): + dialog = Overview_Dialog(self, "TCV", infoDict) + status = dialog.status() + LogStream.logVerbose("status="+status) + if status == "Cancel": + return None + else: + return dialog.getVarDict() + +import Tkinter +class Overview_Dialog(Hazard_HLSTCV_Common.Common_Dialog): + def __init__(self, parent, title, infoDict=None): + Hazard_HLSTCV_Common.Common_Dialog.__init__(self, parent, title, infoDict) + + def body(self, master): + # build the main display dialog + tkObject_dict = self._tkObject_dict + overviewList = self._parent._overview_list() + fontDict = self._parent._font_GUI_dict() + + # OVERVIEW header + headerFG, headerFont = fontDict["headers"] + frame = Tkinter.Frame(master, relief=Tkinter.GROOVE, borderwidth=1) + frame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=Tkinter.NO) + + numBoxes = 2 + + boxes = [] + for i in range(numBoxes): + newBox = Tkinter.Frame(master) + newBox.pack(side=Tkinter.TOP, expand=Tkinter.NO, + fill=Tkinter.Y, anchor=Tkinter.W) + boxes.append(newBox) + + for infoDict in overviewList: + name = infoDict["name"] + label = infoDict["label"] + options = infoDict.get("options", []) + entryField = infoDict.get("entryField", None) + default = infoDict.get("default", None) + optionType = infoDict.get("optionType", "radio") + + index = overviewList.index(infoDict) + if index == 0: + boxNum = 0 + buttonSide=Tkinter.TOP + frameSide = Tkinter.LEFT + else: + boxNum = 1 + buttonSide=Tkinter.LEFT + frameSide=Tkinter.TOP + + box = boxes[boxNum] + + tkObject_dict[name], entryObject = self._makeRadioOrCheckList( + box, label, options, default, buttonSide=buttonSide, frameSide=frameSide, + entryField=entryField, headerFG=headerFG, + headerFont=headerFont, boxType=optionType) + if entryObject is not None: + tkObject_dict[self._entryName(name)] = entryObject + + # End Instructions and Button + frame = Tkinter.Frame(master, relief=Tkinter.GROOVE, borderwidth=1) + self._makeButtons(frame) + frame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=Tkinter.NO) + + def _makeButtons(self, master): + frame = Tkinter.Frame(master) + buttonList = self._parent._GUI1_configDict().get("buttonList", []) + for button, label in buttonList: + if button == "Next": + command = self.okCB + else: # Cancel + command = self.cancelCB + Tkinter.Button(frame, text=label, command=command, width=10, + state=Tkinter.NORMAL).pack(side=Tkinter.LEFT, pady=5, padx=10) + frame.pack() + + def okCB(self): + # pull the data from the tkObject_dict before they get toasted + tkObject_dict = self._tkObject_dict + overviewList = self._parent._overview_list() + for infoDict in overviewList: + name = infoDict["name"] + label = infoDict["label"] + options = infoDict.get("options", []) + entryField = infoDict.get("entryField", None) + default = infoDict.get("default", None) + optionType = infoDict.get("optionType", "radio") + + if optionType == "check": + checkList = [] + ivarList = tkObject_dict[name] + for i in range(len(options)): + if ivarList[i].get(): + checkList.append(options[i]) + value = checkList + self._setVarDict(name, value) + else: + value = tkObject_dict[name].get() + self._setVarDict(name, value, options) + + if entryField is not None: + entryName = self._entryName(name) + self._setVarDict(entryName, tkObject_dict[entryName].get()) + # close window and set status "Ok" + self._status = "Ok" + self.withdraw() + self.ok() + + +class SectionCommon(): + def __init__(self, textProduct, segment, sectionHeaderName): + self._textProduct = textProduct + self._sectionHeaderName = sectionHeaderName + self._segment = segment + + def _setProductPartValue(self, dictionary, productPartName, value): + dictionary[self._sectionName + '._' + productPartName] = value + + def _finalSectionParts(self, segment_vtecRecords_tuple, parts): + finalParts = [] + for partName in parts: + if partName not in self._textProduct._noOpParts(): + finalParts.append(self._sectionName + '._' + partName) + else: + finalParts.append(partName) + + return [{ + 'arguments': segment_vtecRecords_tuple, + 'partsList': finalParts + }] + + def _sectionHeader(self, segmentDict, productSegmentGroup, productSegment): + self._setProductPartValue(segmentDict, 'sectionHeader', self._sectionHeaderName) + + def _lifePropertyThreatSummary(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._maxThreat is not None: + threatLevel = self._stats._maxThreat + if threatLevel == "Mod": + threatLevel = "Moderate" + + self._setProductPartValue(segmentDict, 'lifePropertyThreatSummary', + "Threat to Life and Property: " + threatLevel) + + # SARAH - this new method will convert the single word threat trend into + # an appropriate sentence + def _getThreatTrendSentence(self, section, threatTrendValue): + + if threatTrendValue.upper() == "INCREASING": + text = "The %s threat has increased" % (section) + elif threatTrendValue.upper() == "DECREASING": + text = "The %s threat has decreased" % (section) + elif threatTrendValue.upper() == "NEARLY STEADY": + text = "The %s threat has remained nearly steady" % (section) + + return text + " from the previous assessment." + + def _getThreatTrendValue(self, elementName, magnitudeIncreaseThreshold): + threatKey = elementName + "Threat" + forecastKey = elementName + "Forecast" + + print "SARAH: getThreatTrendValue _currentAdvisory =", self._stats._currentAdvisory + print "SARAH: getThreatTrendValue _previousAdvisory =", self._stats._previousAdvisory + + if (self._stats._currentAdvisory is None) or (self._stats._previousAdvisory is None): + # Only compute a threat trend if we have 2 or more advisories + return None + + currentThreat = self._stats._currentAdvisory[threatKey] + previousThreat = self._stats._previousAdvisory[threatKey] + shorterTermTrendDifference = self._threatDifference(currentThreat, previousThreat) + + print "SARAH: shorterTermTrendDifference =", shorterTermTrendDifference + + previousPreviousThreat = None + longerTermTrendDifference = None + if self._stats._previousPreviousAdvisory is not None: + previousPreviousThreat = self._stats._previousPreviousAdvisory[threatKey] + longerTermTrendDifference = self._threatDifference(currentThreat, previousPreviousThreat) + + threatTrendValue = "NEARLY STEADY" + if self._isThreatDecreasing(shorterTermTrendDifference, longerTermTrendDifference): + threatTrendValue = "DECREASING" + elif self._isThreatIncreasing(shorterTermTrendDifference, longerTermTrendDifference): + threatTrendValue = "INCREASING" + elif currentThreat == "Extreme" and \ + self._advisoriesHaveKey(forecastKey) and \ + self._isMagnitudeIncreasing(self._stats._currentAdvisory[forecastKey], + self._stats._previousAdvisory[forecastKey], + self._stats._previousPreviousAdvisory[forecastKey], + magnitudeIncreaseThreshold): + threatTrendValue = "INCREASING" + + return threatTrendValue + + def _threatDifference(self, threat1, threat2): + threatLevels = self._textProduct.threatKeyOrder() + return threatLevels.index(threat1) - threatLevels.index(threat2) + + def _isThreatDecreasing(self, shorterTermTrendDifference, longerTermTrendDifference): + #If the current threat is at least 1 category lower than both previous advisories + if (shorterTermTrendDifference < 0 and \ + longerTermTrendDifference is not None and \ + longerTermTrendDifference < 0): + return True + #Or if the current threat decreased by more than 1 category + elif shorterTermTrendDifference < -1: + return True + else: + return False + + def _isThreatIncreasing(self, shorterTermTrendDifference, longerTermTrendDifference): + #If the current threat is at least 1 category higher than both previous advisories + if (shorterTermTrendDifference > 0 and \ + longerTermTrendDifference is not None and \ + longerTermTrendDifference > 0): + return True + #Or if the current threat increased by more than 1 category + elif shorterTermTrendDifference > 1: + return True + else: + return False + + def _advisoriesHaveKey(self, key): + return (self._stats._currentAdvisory.has_key(key) and + self._stats._currentAdvisory[key] is not None) and \ + (self._stats._previousAdvisory is not None and + self._stats._previousAdvisory[key] is not None) and \ + (self._stats._previousPreviousAdvisory is not None and + self._stats._previousPreviousAdvisory[key] is not None) + + def _isMagnitudeIncreasing(self, currentValue, previousValue, previousPreviousValue, threshold): + if (currentValue > previousValue and currentValue > previousPreviousValue) or \ + (currentValue - previousValue) >= threshold: + return True + else: + return False + + def _calculateThreatStatementTr(self, onsetHour, endHour, threatTrendValue): + tr = None + + print "SARAH: onset hour =", onsetHour + print "SARAH: end hour =", endHour + print "SARAH: threatTrendValue =", threatTrendValue + + if (onsetHour is not None) and \ + (endHour is not None): + + if onsetHour > 36: + tr = "check plans" + elif onsetHour > 6: + tr = "complete preparations" + elif onsetHour <= 6 and endHour > 0: + tr = "hunker down" + elif (threatTrendValue is not None) and (threatTrendValue.upper() == "DECREASING"): + tr = "recovery" + else: + tr = "nothing to see here" + + return tr + + def _setThreatStatementsProductParts(self, segmentDict, productSegment, tr): + +# print "MATT: tr = %s self._stats.maxThreat = %s" % (repr(tr), +# repr(self._stats.maxThreat)) + if tr is not None and self._stats._maxThreat is not None: + (planning, action, preparation) = self._getThreatStatements(productSegment, + self._sectionHeaderName, + self._stats._maxThreat, + tr) + + self._setProductPartValue(segmentDict, 'threatStatements', + [planning, action, preparation]) + + def _getThreatStatements(self, productSegment, sectionName, maxThreat, tr): + import TCVDictionary + threatStatements = TCVDictionary.ThreatStatements + statements = threatStatements[sectionName][maxThreat][tr] + planning = statements["planning"] + preparation = statements["preparation"] + action = statements["action"] + + # Check for any overrides + try: + planning = threatStatements[sectionName][maxThreat][tr]["planning"] + except KeyError: + pass + + try: + preparation = threatStatements[sectionName][maxThreat][tr]["preparation"] + except KeyError: + pass + + try: + action = threatStatements[sectionName][maxThreat][tr]["action"] + except KeyError: + pass + + return (planning, preparation, action) + + def _potentialImpactsSummary(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._maxThreat is not None: + summary = self._getPotentialImpactsSummaryText(self._stats._maxThreat) + self._setProductPartValue(segmentDict, 'potentialImpactsSummary', summary) + + def _getPotentialImpactsSummaryText(self, maxThreat): + if maxThreat == "Extreme": + impactLevel = "Devastating to Catastrophic" + elif maxThreat == "High": + impactLevel = "Extensive" + elif maxThreat == "Mod": + impactLevel = "Significant" + elif maxThreat == "Elevated": + impactLevel = "Limited" + else: + impactLevel = "None" + + return "Potential Impacts: " + impactLevel + + def _potentialImpactsStatements(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._maxThreat is not None: + statements = self._getPotentialImpactsStatements(productSegment, self._sectionHeaderName, self._stats._maxThreat) + self._setProductPartValue(segmentDict, 'potentialImpactsStatements', statements) + + def _getPotentialImpactsStatements(self, productSegment, elementName, maxThreat): + import TCVDictionary + potentialImpactStatements = TCVDictionary.PotentialImpactStatements + statements = potentialImpactStatements[elementName][maxThreat] + + import TCVAreaDictionary + tcv_AreaDictionary = TCVAreaDictionary.TCV_AreaDictionary + + segment, vtecRecords = productSegment + if segment in tcv_AreaDictionary: + potentialImpactStatements = tcv_AreaDictionary[segment]["potentialImpactsStatements"] + + # Check for any overrides + try: + statements = potentialImpactStatements[elementName][maxThreat] + except KeyError: + pass + + return statements + + def _preparationStatement(self, severityString): + preparationStatement = "" + if severityString == "DEVASTATING" or severityString == "EXTENSIVE IMPACTS": + preparationStatement += "AGGRESSIVE " + + preparationStatement += "PREPARATIONS SHOULD BE MADE FOR CHANCE OF " + + if severityString == "DEVASTATING": + preparationStatement += "DEVASTATING TO CATASTROPHIC" + elif severityString == "EXTENSIVE IMPACTS": + preparationStatement += "EXTENSIVE" + elif severityString == "SIGNIFICANT": + preparationStatement += "SIGNIFICANT" + elif severityString == "LIMITED": + preparationStatement += "LIMITED" + + preparationStatement += " IMPACTS BASED ON LATEST THREAT" + + return preparationStatement + +class WindSection(SectionCommon): + def __init__(self, textProduct, segment, stats): + SectionCommon.__init__(self, textProduct, segment, "Wind") + self._sectionName = 'windSection[\'' + segment + '\']' + self._stats = stats + + def sectionParts(self, segment_vtecRecords_tuple): + parts = [ + 'sectionHeader', + 'forecastSubsection', + 'threatSubsection', + 'impactsSubsection', + ] + + return self._finalSectionParts(segment_vtecRecords_tuple, parts) + + def _forecastSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._latestForecastSummary(subsectionDict, productSegmentGroup, productSegment) + self._peakWind(subsectionDict, productSegmentGroup, productSegment) + self._windowTS(subsectionDict, productSegmentGroup, productSegment) + self._windowHU(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'forecastSubsection', subsectionDict) + + def _latestForecastSummary(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._maxWind is None: + self._setProductPartValue(segmentDict, 'latestForecastSummary', + "No wind forecast") + else: + categoryLabel = None + categories = self._moderatedMaxWindMph_categories() + moderatedMaxWind = self._ktToMph(self._stats._maxWind, "Wind") + for key in categories.keys(): + minVal, maxVal = categories[key] + if minVal <= moderatedMaxWind and moderatedMaxWind < maxVal: + categoryLabel = key + break + + forecastText = "LATEST LOCAL FORECAST: " + if categoryLabel is not None: + forecastText += "Equivalent " + categoryLabel + " force wind" + else: + segment, vtecRecords = productSegment + numRecords = len(vtecRecords) + possibleHazardsFound = False + + for i in range(numRecords): + vtecRecord = vtecRecords[i] + if vtecRecord["phensig"] in ["HU.A", "HU.W", "TR.A", "TR.W"] or \ + self._stats._windowTS is not None: + forecastText += "Tropical storm force winds remain possible" + possibleHazardsFound = True + break + if not possibleHazardsFound: + forecastText += "Below tropical storm force wind" + + self._setProductPartValue(segmentDict, 'latestForecastSummary', forecastText) + + def _peakWind(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._maxWind is not None: + windText = "PEAK WIND FORECAST: " + moderatedMaxWind = self._ktToMph(self._stats._maxWind, "Wind") + if moderatedMaxWind >= 74: + maxRange = 20 + elif moderatedMaxWind >= 58: + maxRange = 15 + elif moderatedMaxWind >= 20: + maxRange = 10 + else: + maxRange = 5 + + windText += str(int(moderatedMaxWind - maxRange)) + "-" + str(int(moderatedMaxWind)) + " mph" + if self._stats._maxGust is not None: + moderatedMaxWindGust = self._ktToMph(self._stats._maxGust, "WindGust") + +# # SARAH - we want to round the wind gust to the nearest 5 kt +# moderatedMaxWindGust = \ +# self._textProduct.round(moderatedMaxWindGust, "Nearest", 5) + + windText += " with gusts to " + str(int(moderatedMaxWindGust)) + " mph" + + self._setProductPartValue(segmentDict, 'peakWind', windText) + + def _windowTS(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._windowTS is not None: + self._setProductPartValue(segmentDict, 'windowTS', self._stats._windowTS) + + def _windowHU(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._windowHU is not None: + self._setProductPartValue(segmentDict, 'windowHU', self._stats._windowHU) + + def _threatSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._lifePropertyThreatSummary(subsectionDict, productSegmentGroup, productSegment) + self._threatTrend(subsectionDict, productSegmentGroup, productSegment) + self._threatStatements(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'threatSubsection', subsectionDict) + + def _threatTrend(self, segmentDict, productSegmentGroup, productSegment): + self._threatTrendValue = \ + self._getThreatTrendValue("Wind", + magnitudeIncreaseThreshold=self._textProduct.mphToKt(20)) + + if self._threatTrendValue is not None: + # Convert the threat trend to a sentence + threatTrendSentence = \ + self._getThreatTrendSentence("wind", self._threatTrendValue) + + self._setProductPartValue(segmentDict, 'threatTrend', + threatTrendSentence) + + def _threatStatements(self, segmentDict, productSegmentGroup, productSegment): + print "SARAH: Wind Threat Statements" + windTr = self._calculateThreatStatementTr(self._stats._onset34Hour, + self._stats._end34Hour, + self._threatTrendValue) +# print "MATT: in _threatStatements tr = %s" % (repr(windTr)) + + if not hasattr(self._textProduct, "_windThreatStatementsTr"): + self._textProduct._windThreatStatementsTr = dict() + + self._textProduct._windThreatStatementsTr[self._segment] = windTr + + self._setThreatStatementsProductParts(segmentDict, productSegment, + windTr) + +# def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): +# subsectionDict = collections.OrderedDict() +# self._potentialImpactsSummary(subsectionDict, productSegmentGroup, productSegment) +# self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) +# if len(subsectionDict) > 0: +# self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + + # SARAH - modified to not include wind impacts during the "recovery" and + # "nothing to see here" phases of the tropical cyclone event + def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): + + # Compute time range to onset + try: + windTr = self._textProduct._windThreatStatementsTr[self._segment] + except: + windTr = self._calculateThreatStatementTr(self._stats._onset34Hour, + self._stats._end34Hour, + self._threatTrendValue) + + # If we are after the hunker down phase + if windTr in ["recovery", "nothing to see here"]: + # Do not include this section at all + return + + subsectionDict = collections.OrderedDict() + self._potentialImpactsSummary(subsectionDict, productSegmentGroup, productSegment) + self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + + ### Supporting functions + def _moderatedMaxWindMph_categories(self): + # Dictionary representing wind thresholds in kts + # for category 1, 2, 3, 4 or 5 hurricanes. + return { + 'CAT 5 Hurricane': (157, 999), + 'CAT 4 Hurricane': (130, 157), + 'CAT 3 Hurricane': (111, 130), + 'CAT 2 Hurricane': ( 96, 111), + 'CAT 1 Hurricane': ( 74, 96), + 'Strong Tropical Storm': ( 58, 73), + 'Tropical Storm': ( 39, 58), + } + + def _ktToMph(self, value, element): + newVal = self._textProduct.ktToMph(value) + newVal = self._textProduct.round(newVal, "Nearest", self._increment(element)) + return newVal + + # This is a very simple way to round values -- if we need + # something more sophisticated, we'll add it later. + def _increment(self, element): + dict = { + "Wind": 5, + "WindGust": 5, + "InundationMax": 0.1, + } + return dict.get(element, 0) + +class StormSurgeSection(SectionCommon): + def __init__(self, textProduct, segment, stats): + SectionCommon.__init__(self, textProduct, segment, "Storm Surge") + self._sectionName = 'stormSurgeSection[\'' + segment + '\']' + self._stats = stats + + def sectionParts(self, segment_vtecRecords_tuple): + parts = [ + 'sectionHeader', + 'forecastSubsection', + 'threatSubsection', + 'impactsSubsection', + ] + + return self._finalSectionParts(segment_vtecRecords_tuple, parts) + + def _forecastSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._latestForecastSummary(subsectionDict, productSegmentGroup, productSegment) + if self._textProduct._PopulateSurge: + self._peakSurge(subsectionDict, productSegmentGroup, productSegment) + self._surgeWindow(subsectionDict, productSegmentGroup, productSegment) + + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'forecastSubsection', subsectionDict) + + def _latestForecastSummary(self, segmentDict, productSegmentGroup, productSegment): + + if not self._textProduct._PopulateSurge: + self._setProductPartValue(segmentDict, 'latestForecastSummary', + "Latest local forecast: Not available at this time. To be updated shortly.") + elif self._stats._inundationMax is None or self._stats._inundationMax < 1: + self._setProductPartValue(segmentDict, 'latestForecastSummary', + "No storm surge inundation forecast") + else: + max = self._stats._inundationMax + summary = "Latest local forecast: " + + if 1 <= max and max < 4: + summary += "Localized" + elif 4 <= max and max < 12: + summary += "Life-threatening" + else: + summary += "Historic" + + self._setProductPartValue(segmentDict, 'latestForecastSummary', + summary + " storm surge flooding") + + def _peakSurge(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._inundationMax is not None and self._stats._inundationMax >= 1: + max = self._stats._inundationMax + if max > 10: + maxRange = 4 + elif max > 6: + maxRange = 3 + elif max > 2: + maxRange = 2 + else: + maxRange = None + + if maxRange is not None: + words = str(int(max - maxRange)) + "-" + str(int(max)) + " feet above ground" + elif max > 0: + + # SARAH - we were getting really weird values of peak surge + # (e.g. "UP TO 1.70000004768 FEET"). This fix will round up + # to the nearest integer value +# words = "Up to " + str(max) + " feet above ground" + words = "Up to " + str(int(max + 0.5)) + " feet above ground" + else: + words = "" + + self._setProductPartValue(segmentDict, 'peakSurge', + "Peak Storm Surge Inundation: " + words + " somewhere within surge prone areas") + + def _surgeWindow(self, segmentDict, productSegmentGroup, productSegment): + if "None" not in self._stats._windowSurge: + self._setProductPartValue(segmentDict, 'surgeWindow', self._stats._windowSurge) + + def _threatSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._lifePropertyThreatSummary(subsectionDict, productSegmentGroup, productSegment) + if self._textProduct._PopulateSurge: + self._threatTrend(subsectionDict, productSegmentGroup, productSegment) + self._threatStatements(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'threatSubsection', subsectionDict) + + def _lifePropertyThreatSummary(self, segmentDict, productSegmentGroup, productSegment): + if not self._textProduct._PopulateSurge: + self._setProductPartValue(segmentDict, 'lifePropertyThreatSummary', + "Threat to Life and Property: Not available at this time. To be updated shortly.") + else: + SectionCommon._lifePropertyThreatSummary(self, segmentDict, productSegmentGroup, productSegment) + + def _threatTrend(self, segmentDict, productSegmentGroup, productSegment): + self._threatTrendValue = self._getThreatTrendValue("StormSurge", magnitudeIncreaseThreshold=4) + + if self._threatTrendValue is not None: + # Convert the threat trend to a sentence + threatTrendSentence = \ + self._getThreatTrendSentence("storm surge", self._threatTrendValue) + + self._setProductPartValue(segmentDict, 'threatTrend', + threatTrendSentence) + + def _threatStatements(self, segmentDict, productSegmentGroup, productSegment): + print "SARAH: Surge Threat Statements" + surgeTr = self._calculateThreatStatementTr(self._stats._onsetSurgeHour, + self._stats._endSurgeHour, + self._threatTrendValue) + + self._setThreatStatementsProductParts(segmentDict, productSegment, + surgeTr) + + def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._potentialImpactsSummary(subsectionDict, productSegmentGroup, productSegment) + if self._textProduct._PopulateSurge: + self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) + + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + + def _potentialImpactsSummary(self, segmentDict, productSegmentGroup, productSegment): + if not self._textProduct._PopulateSurge: + + # SARAH - We do not want the '(For plausible worst case)' in the text +# self._setProductPartValue(segmentDict, 'potentialImpactsSummary', +# "Potential Impacts (For plausible worst case): Not available at this time. To be updated shortly.") + self._setProductPartValue(segmentDict, 'potentialImpactsSummary', + "Potential Impacts: Not available at this time. To be updated shortly.") + else: + SectionCommon._potentialImpactsSummary(self, segmentDict, productSegmentGroup, productSegment) + + +class FloodingRainSection(SectionCommon): + def __init__(self, textProduct, segment, stats): + SectionCommon.__init__(self, textProduct, segment, "Flooding Rain") + self._sectionName = 'floodingRainSection[\'' + segment + '\']' + self._stats = stats + + def sectionParts(self, segment_vtecRecords_tuple): + parts = [ + 'sectionHeader', + 'forecastSubsection', + 'threatSubsection', + 'impactsSubsection', + ] + + return self._finalSectionParts(segment_vtecRecords_tuple, parts) + + def _forecastSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._latestForecastSummary(subsectionDict, productSegmentGroup, productSegment) + self._peakRain(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'forecastSubsection', subsectionDict) + + def _latestForecastSummary(self, segmentDict, productSegmentGroup, productSegment): + summary = "No flood watch is in effect" + segment, vtecRecords = productSegment + + headlines, _ = self._textProduct._getAdditionalHazards() + headlineList = self._textProduct._checkHazard(headlines, + [("FA","A"),("FF","A")], + returnList = True) + + if len(headlineList) != 0: + # Extract the first flood headline out (there will only be 1 in effect at a time) + (key, areaList) = headlineList[0] + (headline, _, _, _) = key + + # Make sure it is for our zone + if self._segment in areaList: + summary = headline + " is in effect" + + self._setProductPartValue(segmentDict, 'latestForecastSummary', + "Latest Local Forecast: " + summary) + + def _peakRain(self, segmentDict, productSegmentGroup, productSegment): + if self._stats._sumAccum is not None: + words = self._rainRange(int(math.ceil(self._stats._sumAccum))) + self._setProductPartValue(segmentDict, 'peakRain', "Peak Rainfall Amounts: " + words) + + def _rainRange(self, sumAccum): + minAccum = 0 + maxAccum = 0 + + if sumAccum == 0: + return "No significant rainfall forecast" + elif sumAccum == 1: + return "around 1 inch" + elif sumAccum == 2: + minAccum, maxAccum = (1, 3) + elif sumAccum == 3: + minAccum, maxAccum = (2, 4) + elif sumAccum == 4: + minAccum, maxAccum = (3, 5) + elif sumAccum in [5,6,7]: + minAccum, maxAccum = (4, 8) + elif sumAccum in [8,9]: + minAccum, maxAccum = (6, 10) + elif sumAccum in [10,11]: + minAccum, maxAccum = (8, 12) + elif sumAccum in [12,13]: + minAccum, maxAccum = (10, 14) + elif sumAccum in [14,15,16,17]: + minAccum, maxAccum = (12, 18) + elif 17 < sumAccum and sumAccum < 25: + minAccum, maxAccum = (18, 24) + else: + return "More than two feet" + + return "%d-%d inches...with locally higher amounts" % (minAccum, maxAccum) + + def _threatSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._lifePropertyThreatSummary(subsectionDict, productSegmentGroup, productSegment) + self._threatTrend(subsectionDict, productSegmentGroup, productSegment) + self._threatStatements(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'threatSubsection', subsectionDict) + + def _threatTrend(self, segmentDict, productSegmentGroup, productSegment): + self._threatTrendValue = self._getThreatTrendValue("FloodingRain", magnitudeIncreaseThreshold=4) + + if self._threatTrendValue is not None: + # Convert the threat trend to a sentence + threatTrendSentence = \ + self._getThreatTrendSentence("flooding rain", self._threatTrendValue) + + self._setProductPartValue(segmentDict, 'threatTrend', + threatTrendSentence) + + def _threatStatements(self, segmentDict, productSegmentGroup, productSegment): + tr = self._textProduct._windThreatStatementsTr[self._segment] + + self._setThreatStatementsProductParts(segmentDict, productSegment, tr) + + def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._potentialImpactsSummary(subsectionDict, productSegmentGroup, productSegment) + self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + +class TornadoSection(SectionCommon): + def __init__(self, textProduct, segment, stats): + SectionCommon.__init__(self, textProduct, segment, "Tornado") + self._sectionName = 'tornadoSection[\'' + segment + '\']' + self._stats = stats + + def sectionParts(self, segment_vtecRecords_tuple): + parts = [ + 'sectionHeader', + 'forecastSubsection', + 'threatSubsection', + 'impactsSubsection', + ] + + return self._finalSectionParts(segment_vtecRecords_tuple, parts) + + def _forecastSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._latestForecastSummary(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'forecastSubsection', subsectionDict) + + def _latestForecastSummary(self, segmentDict, productSegmentGroup, productSegment): + summary = "No Tornado Watch is in effect" + segment, vtecRecords = productSegment + + headlines, _ = self._textProduct._getAdditionalHazards() + headlineList = self._textProduct._checkHazard(headlines, + [("TO","A")], + returnList = True) + if len(headlineList) != 0: + # Extract the first tornado headline out (there will only be 1 in effect at a time) + (key, areaList) = headlineList[0] + (headline, _, _, _) = key + + # Make sure it is for our zone + if self._segment in areaList: + summary = "Tornado Watch is in effect" + + self._setProductPartValue(segmentDict, 'latestForecastSummary', + "Latest Local Forecast: " + summary) + + def _threatSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._lifePropertyThreatSummary(subsectionDict, productSegmentGroup, productSegment) + self._threatTrend(subsectionDict, productSegmentGroup, productSegment) + self._threatStatements(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'threatSubsection', subsectionDict) + + def _threatTrend(self, segmentDict, productSegmentGroup, productSegment): + self._threatTrendValue = self._getThreatTrendValue("Tornado", + magnitudeIncreaseThreshold=None) + + if self._threatTrendValue is not None: + # Convert the threat trend to a sentence + threatTrendSentence = \ + self._getThreatTrendSentence("tornado", self._threatTrendValue) + + self._setProductPartValue(segmentDict, 'threatTrend', + threatTrendSentence) + + def _threatStatements(self, segmentDict, productSegmentGroup, productSegment): + tr = self._textProduct._windThreatStatementsTr[self._segment] + + self._setThreatStatementsProductParts(segmentDict, productSegment, tr) + + def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): + subsectionDict = collections.OrderedDict() + self._potentialImpactsSummary(subsectionDict, productSegmentGroup, productSegment) + self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) + if len(subsectionDict) > 0: + self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + + +from xml.etree.ElementTree import Element, SubElement, tostring, dump +import xml.dom.minidom as minidom +import re +class XMLFormatter(): + def __init__(self, textProduct): + self._textProduct = textProduct + + def execute(self, productDict): + xml = Element('product') + self.dictionary(xml, productDict) + print "SARAH: XML =", xml + print "SARAH: XML dump =", dump(xml) + prettyXML = minidom.parseString(tostring(xml)) + return prettyXML.toprettyxml() #tostring(xml) + + def xmlKeys(self): + return [ + 'wmoHeader', + 'TTAAii', + 'originatingOffice', + 'productID', + 'siteID', + 'fullStationID', + 'ddhhmmTime', + 'easMessage', + 'productHeader', + 'disclaimer', + 'cityState', + 'stormNumber', + 'productName', + 'stormName', + 'advisoryType', + 'advisoryNumber', + 'issuedByString', + 'issuanceTimeDate', + + 'segments', + 'ugcCodes', + 'ugcHeader', + 'vtecRecords', + 'areaList', + 'issuanceTimeDate', + 'summaryHeadlines', + 'headlinesInEffect', + 'headlineDefinitions', + 'locationsAffected', + 'fcstConfidence', + #section keys will be inserted here (see sectionKeys) + 'infoSection', + + 'endProduct', + ] + + def sectionKeys(self): + return [ + 'windSection', + 'sectionHeader', + 'forecastSubsection', + 'latestForecastSummary', + 'peakWind', + 'windowTS', + 'windowHU', + 'threatSubsection', + 'lifePropertyThreatSummary', + 'threatTrend', + 'threatStatements', + 'impactsSubsection', + 'potentialImpactsSummary', + 'potentialImpactsStatements', + + 'stormSurgeSection', + 'sectionHeader', + 'forecastSubsection', + 'latestForecastSummary', + 'peakSurge', + 'surgeWindow', + 'threatSubsection', + 'lifePropertyThreatSummary', + 'threatTrend', + 'threatStatements', + 'impactsSubsection', + 'potentialImpactsSummary', + 'potentialImpactsStatements', + + 'floodingRainSection', + 'sectionHeader', + 'forecastSubsection', + 'latestForecastSummary', + 'peakRain', + 'threatSubsection', + 'lifePropertyThreatSummary', + 'threatTrend', + 'threatStatements', + 'impactsSubsection', + 'potentialImpactsSummary', + 'potentialImpactsStatements', + + 'tornadoSection', + 'sectionHeader', + 'forecastSubsection', + 'latestForecastSummary', + 'threatSubsection', + 'lifePropertyThreatSummary', + 'threatStatements', + 'impactsSubsection', + 'potentialImpactsSummary', + 'potentialImpactsStatements', + ] + + def getSectionKey(self, key): + sectionKey = re.sub("\['......'\]", "", key) + + if "._" in sectionKey: + sectionKey = re.sub(".*\._", "", sectionKey) + + print "SARAH: sectionKey =", sectionKey + return sectionKey + + def dictionary(self, xml, productDict): + ''' + Returns the dictionary in XML format. + @param productDict: dictionary values + @return: Returns the dictionary in XML format. + ''' + if productDict is not None: + for key in productDict: + value = productDict[key] + editable = False +# if isinstance(key, KeyInfo): +# editable = key.isEditable() +# key = key.getName() + + if key not in self.xmlKeys(): + sectionKey = self.getSectionKey(key) + if sectionKey not in self.sectionKeys(): + print "SARAH: skipping", key, "in XML" + continue + else: + key = sectionKey + if isinstance(value, dict): + subElement = SubElement(xml,key) + self.dictionary(subElement, value) + elif isinstance(value, list): + if key == 'cityList': + subElement = SubElement(xml,'cityList') + if editable: + subElement.attrib['editable'] = 'true' + self.list(subElement, 'city', value) +# elif key == 'infoSection': +# subElement = SubElement(xml, key) +# legacyFormatter = LegacyFormatter(self._textProduct) +# legacyText = legacyFormatter.processInfoSection(value) +# legacyText = legacyText.encode('string-escape') +# subElement.text = legacyText +# if editable: +# subElement.attrib['editable'] = 'true' + else: + self.list(xml, key, value) + else: + subElement = SubElement(xml,key) + subElement.text = value + if editable: + subElement.attrib['editable'] = 'true' + + def list(self, xml, key, data): + ''' + Returns the list in XML format. + @param data: list of values + @return: Returns the list in XML format. + ''' + editable = False +# if isinstance(key, KeyInfo): +# editable = key.isEditable() +# key = key.getName() + if data is not None: + if 'info' in key and 'Section' in key: + subElement = SubElement(xml, key) + print "SARAH: info key =", key + print "SARAH: value =", data + if isinstance(data, list): + subkey = 'info' + 'Sub' + key[4:] + for value in data: + self.list(subElement, subkey, value) + else: + subElement.text = data + else: + for value in data: + + subElement = SubElement(xml, key) + if editable: + subElement.attrib['editable'] = 'true' + + if isinstance(value, dict): + self.dictionary(subElement, value) + elif isinstance(value, list): + if key == 'cityList': + subElement = SubElement(xml,'cityList') + if editable: + subElement.attrib['editable'] = 'true' + self.list(subElement, 'city', value) + else: + self.list(xml, key, value) + else: + subElement.text = value + + +class LegacyFormatter(): + def __init__(self, textProduct): + self._textProduct = textProduct + self.TAB = " "*self._textProduct._tabLength + self._tpc = Hazard_HLSTCV_Common.TextProductCommon() + + def execute(self, productDict): + self.productDict = productDict + productParts = self._tpc.getVal(productDict, 'productParts', []) + text = self._processProductParts(productDict, productParts.get('partsList')) + return text + + def _processProductParts(self, productDict, productParts, skipParts=[]): + ''' + Adds the product parts to the product + @param productDict -- dictionary of information -- could be the product dictionary or a sub-part such as a segment + @param skipParts -- necessary to avoid repetition when calling this method recursively + @param productParts -- list of instances of the ProductPart class with information about how to format each product part + @return text -- product string + ''' + text = '' + print "SARAH: productParts =", productParts + for part in productParts: + valtype = type(part) + if valtype is str: + name = part + elif valtype is tuple: + name = part[0] + infoDicts = part[1] + print "SARAH: name =", str(name) + print "SARAH: infoDicts =", infoDicts + newtext = self.processSubParts(productDict.get(name), infoDicts) + print "SARAH: newtext type =", type(newtext) + print "SARAH: newtext =", repr(newtext) + text += newtext + continue + elif valtype is list: + print 'GOT HERE -- found list' + self._tpc.flush() + # TODO THIS SHOULD BE REMOVED AFTER THE REFACTOR OF HazardServicesProductGenerationHandler.JAVA + tup = (part[0], part[1]) + part = tup + name = part[0] + + + if name == 'wmoHeader': + text += self.processWmoHeader(productDict['wmoHeader']) + '\n' + elif name == 'easMessage': + text += productDict['easMessage'] + '\n' + elif name == 'productHeader': + text += self.processProductHeader(productDict['productHeader']) + elif name == 'vtecRecords': + for vtecString in productDict['vtecRecords']: + text += vtecString + '\n' + elif name == 'areaList': + text += self._textProduct.indentText(productDict['areaList'], '', '', + maxWidth=self._textProduct._lineLength) + elif name == 'issuanceTimeDate': + text += productDict['issuanceTimeDate'] + '\n\n' + elif name == 'summaryHeadlines': + text += self.processSummaryHeadlines(productDict['summaryHeadlines']) + elif name == 'locationsAffected': + text += self.processLocationsAffected(productDict['locationsAffected']) + elif 'sectionHeader' in name: + text += "* " + productDict[name] + "\n" + elif 'Subsection' in name: + text += self.processSubsection(productDict[name]) + elif name == 'infoSection': + text += self.processInfoSection(productDict['infoSection']) + elif name == 'endProduct': + text += '$$\n' + elif name == 'CR': + text += '\n' + elif name == 'doubleAmpersand': + text += '&&\n' + elif name not in self._noOpParts(): + textStr = productDict.get(name) + print "SARAH: name =", name + print "SARAH: textStr =", textStr + if textStr: + text += textStr + '\n' + return text + + def _noOpParts(self): + ''' + These represent product parts that should be skipped when calling product part methods. + They will be handled automatically by the formatters. + ''' + return ["setup_segment"] #['CR', 'endProduct', 'endSegment', 'issuanceDateTime', 'doubleAmpersand'] + + def processWmoHeader(self, wmoHeader): + text = wmoHeader['TTAAii'] + ' ' + wmoHeader['fullStationID'] + ' ' + wmoHeader['ddhhmmTime'] + '\n' + text += wmoHeader['productID'] + wmoHeader['siteID'] + '\n' + return text + + def processProductHeader(self, headerDict): + text = headerDict['stormName'] + ' ' + headerDict['productName'] + + advisoryText = '' + if headerDict['advisoryType'] is not None and \ + headerDict['advisoryType'].lower() in ["intermediate", "special"]: + advisoryText = headerDict['advisoryType'] + ' ' + + if headerDict['advisoryNumber'] is not None: + advisoryText += 'Advisory Number ' + headerDict['advisoryNumber'] + + if len(advisoryText) > 0: + if len(text + "/" + advisoryText) > self._textProduct._lineLength: + text += '\n' + else: + text += '/' + + text += advisoryText + '\n' + else: + text += '\n' + + text += "National Weather Service " + headerDict['cityState'] + " " + headerDict['stormNumber'] + '\n' + text += headerDict['issuanceTimeDate'] + '\n\n' + + return text + + def processLocationsAffected(self, locationsAffectedList): + if len(locationsAffectedList) == 0: + return "" + + text = "* Locations Affected\n" + for location in locationsAffectedList: + text += self.TAB + "- " + location + "\n" + return text + "\n" + + def processSubsection(self, subsectionOrderedDict): + text = "" + for partName in subsectionOrderedDict: + if "Summary" in partName: + firstIndentText = self.TAB + "- " + nextIndentText = self.TAB + " " + text += self._textProduct.indentText(subsectionOrderedDict[partName], + firstIndentText, + nextIndentText, + maxWidth = self._textProduct._lineLength) + else: + firstIndentText = self.TAB*2 + "- " + nextIndentText = self.TAB*2 + " " + if "threatStatements" in partName: + text += self.processThreatStatements(firstIndentText, + nextIndentText, + subsectionOrderedDict[partName]) + elif "potentialImpactsStatements" in partName: + text += self.processImpactsStatements(firstIndentText, + nextIndentText, + subsectionOrderedDict[partName]) + else: + text += self._textProduct.indentText(subsectionOrderedDict[partName], + firstIndentText, + nextIndentText, + maxWidth=self._textProduct._lineLength) + + return text + "\n" + + def processThreatStatements(self, firstIndentText, nextIndentText, threatStatements): + planning = threatStatements[0] + text = self._textProduct.indentText(planning, + firstIndentText, + nextIndentText, + maxWidth=self._textProduct._lineLength) + + preparation = threatStatements[1] + text += self._textProduct.indentText(preparation, + firstIndentText, + nextIndentText, + maxWidth=self._textProduct._lineLength) + + action = threatStatements[2] + text += self._textProduct.indentText(action, + firstIndentText, + nextIndentText, + maxWidth=self._textProduct._lineLength) + + return text + + def processImpactsStatements(self, firstIndentText, nextIndentText, statements): + text = "" + + for statement in statements: + text += self._textProduct.indentText(statement, + firstIndentText, + nextIndentText, + maxWidth=self._textProduct._lineLength) + + return text + + def processInfoSection(self, infoSection): + if len(infoSection) == 0: + return "" + + text = "* For more information:\n" + text += self._buildInfoSection(infoSection, tabLevel=1) + return text + "\n" + + def _buildInfoSection(self, infoSection, tabLevel): + text = "" + for component in infoSection: + if type(component) is str: + text += self.TAB*tabLevel + "- " + component + "\n" + elif type(component) is list: + text += self._buildInfoSection(component, tabLevel+1) + return text + + def formatIssueTime(self): + text = '' + sentTimeZ = self._tpc.getVal(self.productDict, 'sentTimeZ_datetime') + timeZones = self._tpc.getVal(self.productDict, 'timeZones') + for timeZone in timeZones: + text += self._tpc.formatDatetime(sentTimeZ, '%I%M %p %Z %a %e %b %Y', timeZone) + '\n' + return text + + def processSummaryHeadlines(self, summaryDict): + text = "" + for headline in summaryDict['headlinesInEffect']: + text += headline + "\n" + + text += "\n" + + for definition in summaryDict['headlineDefinitions']: + text += self._textProduct.indentText(definition, + maxWidth=self._textProduct._lineLength) \ + + "\n" + return text + + def processSubParts(self, subParts, infoDicts): + """ + Generates Legacy text from a list of subParts e.g. segments or sections + @param subParts: a list of dictionaries for each subPart + @param partsLists: a list of Product Parts for each segment + @return: Returns the legacy text of the subParts + """ + text = '' + for i in range(len(subParts)): + print "SARAH: subpart subParts[i] =", subParts[i] + print "SARAH: subpart infoDicts[i] =", infoDicts[i] + newtext = self._processProductParts(subParts[i], infoDicts[i].get('partsList')) + print "SARAH: subpart newtext type =", type(newtext) + print "SARAH: subpart newtext =", repr(newtext) + text += newtext + return text + diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/utility/Hazard_NewHLS_Site_MultiPil_Definition.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/utility/Hazard_NewHLS_Site_MultiPil_Definition.py new file mode 100644 index 0000000000..9e2840dabe --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/utility/Hazard_NewHLS_Site_MultiPil_Definition.py @@ -0,0 +1,44 @@ +# --------------------------------------------------------------------- +# 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. +# +# Hazard_NewHLS___Definition.TextUtility +# +# This file sets up all the Product Definition overrides for the +# Hazard_NewHLS formatter for a site. +# +# --------------------------------------------------------------------- + +#********************************************************************** +# MAKE NO CHANGES HERE +# The minimum content of this file is the following Definition statement + +Definition = {} + +# End MAKE NO CHANGES HERE +#********************************************************************** +##################################################### +# Override VariableList if desired +# +#VariableList = [] + +#----- WFO NewHLS Definition ----- +# Definition Statements must start in column 1. + +# REQUIRED CONFIGURATION ITEMS +#Definition['displayName'] = None +Definition['displayName'] = "Hazard_NewHLS_ (Hurricane Local Statement)" + +# Header configuration items +Definition["fullStationID"] = "" # full station identifier (4letter) +Definition["wmoID"] = "" # WMO ID +Definition["pil"] = "" # product pil +Definition["textdbPil"] = "" # Product ID for storing to AWIPS text database. +Definition["awipsWANPil"] = "" # Product ID for transmitting to AWIPS WAN. +Definition["outputFile"] = "{prddir}/TEXT/NewHLS_.txt" + +# OPTIONAL CONFIGURATION ITEMS +#Definition["database"] = "Official" # Source database. "Official", "Fcst", or "ISC" +#Definition["debug"] = 1 + diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/utility/Hazard_TCV_Site_MultiPil_Definition.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/utility/Hazard_TCV_Site_MultiPil_Definition.py new file mode 100644 index 0000000000..4242d8a2bd --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/utility/Hazard_TCV_Site_MultiPil_Definition.py @@ -0,0 +1,44 @@ +# --------------------------------------------------------------------- +# 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. +# +# Hazard_TCV___Definition.TextUtility +# +# This file sets up all the Product Definition overrides for the +# Hazard_TCV formatter for a site. +# +# --------------------------------------------------------------------- + +#********************************************************************** +# MAKE NO CHANGES HERE +# The minimum content of this file is the following Definition statement + +Definition = {} + +# End MAKE NO CHANGES HERE +#********************************************************************** +##################################################### +# Override VariableList if desired +# +#VariableList = [] + +#----- WFO TCV Definition ----- +# Definition Statements must start in column 1. + +# REQUIRED CONFIGURATION ITEMS +#Definition['displayName'] = None +Definition['displayName'] = "Hazard_TCV_ (Tropical Cyclone VTEC)" + +# Header configuration items +Definition["fullStationID"] = "" # full station identifier (4letter) +Definition["wmoID"] = "" # WMO ID +Definition["pil"] = "" # product pil +Definition["textdbPil"] = "" # Product ID for storing to AWIPS text database. +Definition["awipsWANPil"] = "" # Product ID for transmitting to AWIPS WAN. +Definition["outputFile"] = "{prddir}/TEXT/TCV_.txt" + +# OPTIONAL CONFIGURATION ITEMS +#Definition["database"] = "Official" # Source database. "Official", "Fcst", or "ISC" +#Definition["debug"] = 1 +