diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCStormSurgeThreat.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCStormSurgeThreat.py index 9bb095f007..cd7dbf9ae4 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCStormSurgeThreat.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCStormSurgeThreat.py @@ -3,7 +3,7 @@ # support, and with no warranty, express or implied, as to its usefulness for # any purpose. # -# CoastalThreat +# StormSurgeThreat # # Author: Tom LeFebvre/Pablo Santos # April 20, 2012 - To use gridded MSL TO NAVD and MSL to MLLW @@ -19,13 +19,13 @@ # Sept 18, 2014: Added code to pull grids from NHC via ISC if PHISH not # Available on time. Left inactive (commented out) for the moment until that can be fully tested later # in 2014 or in 2015. -# May 22, 2015 (LEFebvre/Santos): Added option to create null grids and manual grids when -# PSURGE not available. Added checks for current guidance for PHISH and ISC options. # # Last Modified: LeFebvre/Santos, July 27, 2015: Expanded Manual options to include Replace and Add options. # This allows sites to specify manually different threat levels across different edit areas and time ranges. # See 2015HTIUserGuide for details. # +# Feb 11, 2016 LeFebvre (16.1.2): Added code to create zero grids and manual grids when +# PSURGE not available. Added checks for current guidance for PHISH and ISC options. # ---------------------------------------------------------------------------- # The MenuItems list defines the GFE menu item(s) under which the # Procedure is to appear. @@ -185,12 +185,16 @@ class Procedure (SmartScript.SmartScript): weName = "Surge" + pctStr + "Pctincr" #print "Attempting to retrieve: ", weName, level - trList = self.getWEInventory(dbName, weName, level) - - if len(trList) == 0: - self.statusBarMsg("No grids available for model:" + dbName, "S") - return None + # get the StormSurgeProb inventory + surgeTRList = self.getWEInventory(dbName, weName, level) + if len(surgeTRList) == 0: + self.statusBarMsg("No PHISH grid found.", "U") + return + # Make timeRanges for all 13 grids. Start with the beginning of the first Phish grid + baseTime = int(surgeTRList[0].startTime().unixTime() / (6 * 3600)) * (6 * 3600) #snap to 6 hour period + trList = self.makeTimingTRs(baseTime) + n = 1 for tr in trList: start = tr.startTime().unixTime() - 6*3600 @@ -203,7 +207,13 @@ class Procedure (SmartScript.SmartScript): end = tr.startTime().unixTime() tr6 = TimeRange.TimeRange(AbsTime.AbsTime(start), AbsTime.AbsTime(end)) - phishGrid = self.getGrids(dbName, weName, level, tr) + + surgeTR = TimeRange.TimeRange(tr.startTime(), AbsTime.AbsTime(tr.startTime().unixTime() + 3600)) + if surgeTR in surgeTRList: + phishGrid = self.getGrids(dbName, weName, level, surgeTR) + else: + phishGrid = np.zeros(self.getGridShape(), 'f') + # # For consistency we need to add smoothing here too as we do in execute. # @@ -214,7 +224,7 @@ class Procedure (SmartScript.SmartScript): if smoothThreatGrid is "Yes": phishGrid = np.where(np.greater(phishGrid, 0.0), self.smoothGrid(phishGrid,3), phishGrid) - grid = np.where(phishGrid>-100,phishGrid*3.28, -80.0) + grid = np.where(phishGrid>-100, phishGrid*3.28, -80.0) # Convert units from meters to feet self.createGrid(mutableID, "InundationTiming", "SCALAR", grid, tr6, precision=1) return @@ -610,7 +620,7 @@ class Procedure (SmartScript.SmartScript): print "Timing grid not found at:", trList[i] if (start - baseTime) / 3600 >= inunStartHour and (end - baseTime) / 3600 <= inunEndHour: - timingGrids[i][modifyMask] = inundationHeight # populate where needed + timingGrids[i][modifyMask] = inundationHeight # populate only where needed self.createGrid(mutableID, "InundationTiming", "SCALAR", timingGrids[i], trList[i]) diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py index 36fefa3ffc..8e58a664c2 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py @@ -1,4 +1,4 @@ -# Version 2016.01.26-0 +# Version 2016.02.09-0 import GenericHazards import string, time, os, re, types, copy, LogStream, collections @@ -2096,7 +2096,7 @@ class TextProduct(HLSTCV_Common.TextProduct): for label, latLon in refList: lat, lon = latLon localRef = self._calcReference(lat0, lon0, lat, lon) - localRef = localRef + " OF " + label + localRef = localRef + " of " + label localRef = localRef.replace(",","") localRefs.append(localRef) return localRefs 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 index fbf3ce77e7..2d3f9e3f6c 100644 --- 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 @@ -1,4 +1,4 @@ -# Version 2016.01.26-0 +# Version 2016.02.09-0 import GenericHazards import JsonSupport @@ -513,14 +513,14 @@ class TextProduct(HLSTCV_Common.TextProduct): gridChecks += [(self._isCorrectNumGrids, "WindThreat", 1, argDict), (self._isContinuousDuration, "Wind", 120, argDict), (self._isContinuousDuration, "WindGust", 120, argDict), - (self._isContinuousDuration, "pws34int", 120, argDict), - (self._isContinuousDuration, "pws64int", 120, argDict), - (self._isCombinedContinuousDuration, "pwsD34", "pwsN34", 96, argDict), - (self._isCombinedContinuousDuration, "pwsD64", "pwsN64", 96, argDict),] + (self._isContinuousDuration, "pws34int", 114, argDict), + (self._isContinuousDuration, "pws64int", 114, argDict), + (self._isCombinedContinuousDuration, "pwsD34", "pwsN34", 102, argDict), + (self._isCombinedContinuousDuration, "pwsD64", "pwsN64", 102, argDict),] if self._PopulateSurge and len(self._coastalAreas()) != 0: gridChecks += [(self._isCorrectNumGrids, "InundationMax", 1, argDict), - (self._isCorrectNumGrids, "InundationTiming", 13, argDict),] + (self._isCorrectNumGrids, "InundationTiming", 12, argDict),] missingGridErrors = [] for gridCheck in gridChecks: @@ -530,9 +530,9 @@ class TextProduct(HLSTCV_Common.TextProduct): error = "" if gridCheck[0] == self._isCorrectNumGrids: if gridCheck[2] == 1: - error = "%s needs exactly 1 grid" % (gridCheck[1]) + error = "%s needs at least 1 grid" % (gridCheck[1]) else: - error = "%s needs exactly %s grids" % (gridCheck[1], gridCheck[2]) + error = "%s needs at least %s grids" % (gridCheck[1], gridCheck[2]) elif gridCheck[0] == self._isContinuousDuration: error = "%s needs at least %s continuous hours worth of data" % (gridCheck[1], gridCheck[2]) else: @@ -575,9 +575,9 @@ class TextProduct(HLSTCV_Common.TextProduct): else: gridTimes.append(gridTime) - self.debug_print("Length of grid times: %s" % (self._pp.pformat(len(gridTimes))), 1) + self.debug_print("Actual number of grids: %s" % (self._pp.pformat(len(gridTimes))), 1) - return len(gridTimes) == expectedNumGrids + return len(gridTimes) >= expectedNumGrids def _isContinuousDuration(self, weatherElement, minimumNumHours, argDict): return self._checkContinuousDuration([weatherElement], minimumNumHours, argDict) @@ -1094,13 +1094,15 @@ class TextProduct(HLSTCV_Common.TextProduct): def _initializeSegmentZoneData(self, segment): # The current advisory will be populated when getting a section's stats self._currentAdvisory['ZoneData'][segment] = { - "WindThreat": None, - "WindForecast": None, - "StormSurgeThreat": None, - "StormSurgeForecast": None, - "FloodingRainThreat": None, - "FloodingRainForecast": None, - "TornadoThreat": None, + "WindThreat": None, + "WindForecast": None, + "WindHighestPhaseReached": None, + "StormSurgeThreat": None, + "StormSurgeForecast": None, + "StormSurgeHighestPhaseReached": None, + "FloodingRainThreat": None, + "FloodingRainForecast": None, + "TornadoThreat": None, } def _getPreviousAdvisories(self): @@ -1327,6 +1329,11 @@ class SectionCommon(): def _isThreatNoneForEntireStorm(self, threatName): previousAdvisories = self._textProduct._getPreviousAdvisories() + + # For the first advisory, this needs to be false otherwise + # potential impacts could be wrong + if len(previousAdvisories) == 0: + return False for advisory in previousAdvisories: if advisory["ZoneData"][self._segment][threatName] != "None": @@ -1514,6 +1521,9 @@ class SectionCommon(): if tr == "default": records = self._textProduct._getVtecRecords(self._segment) for record in records: + if self._textProduct._currentAdvisory['ZoneData'][self._segment]["WindThreat"] != "None": + tr = "hunker down" + break if record["phen"] in ["HU", "TR"] and record["sig"] == "W": if record["act"] == "CAN": tr = "recovery" @@ -1530,6 +1540,30 @@ class SectionCommon(): section == "Wind" and \ self._pastWindHazardWasCAN(): tr = "recovery" + + # --------------------------------------------------------------------- + # Don't allow the event to regress to an earlier phase for this section + + # "default" isn't ordered because it can occur at multiple points before the recovery phase + phaseOrder = [None, "check plans", "complete preparations", "hunker down", "recovery"] + + if self._sectionHeaderName == "Storm Surge": + highestPhaseReachedField = "StormSurgeHighestPhaseReached" + else: # Flooding Rain and Tornado are tied to Wind so that's why they use Wind's phase + highestPhaseReachedField = "WindHighestPhaseReached" + + if tr == "default": + if self._textProduct._currentAdvisory['ZoneData'][self._segment][highestPhaseReachedField] == "recovery": + tr = "recovery" + else: + highestPhaseIndex = \ + phaseOrder.index(self._textProduct._currentAdvisory['ZoneData'][self._segment][highestPhaseReachedField]) + currentPhaseIndex = phaseOrder.index(tr) + + if currentPhaseIndex < highestPhaseIndex: + tr = self._textProduct._currentAdvisory['ZoneData'][self._segment][highestPhaseReachedField] + elif currentPhaseIndex > highestPhaseIndex: + self._textProduct._currentAdvisory['ZoneData'][self._segment][highestPhaseReachedField] = tr return tr @@ -1592,7 +1626,7 @@ class SectionCommon(): with open("/awips2/cave/etc/gfe/userPython/utilities/TCVDictionary.py", 'r') as pythonFile: fileContents = pythonFile.read() exec(fileContents) - + # ThreatStatements comes from TCVDictionary.py when it is exec'ed threatStatements = ThreatStatements @@ -2840,6 +2874,7 @@ class StormSurgeSectionStats(SectionCommonStats): self._setStats(intersectStatList, timeRangeList) def _setStats(self, statList, timeRangeList): + windows = [] phishStartTime = None phishEndTime = None @@ -2867,7 +2902,7 @@ class StormSurgeSectionStats(SectionCommonStats): curPhish = self._textProduct._getStatValue(statDict, "InundationTiming", "Max") self._textProduct.debug_print("curPhish = '%s'" % (str(curPhish)), 1) - self._textProduct.debug_print("phishStartTime = %s phishEndTime = %s" % + self._textProduct.debug_print("phishStartTime = %s phishEndTime = %s" % (str(phishStartTime), str(phishEndTime)), 1) if (curPhish is None) or (curPhish == 'None'): @@ -2924,13 +2959,39 @@ class StormSurgeSectionStats(SectionCommonStats): phishStartTime = tr.startTime() elif endCondition and (phishStartTime is not None) and (phishEndTime is None): phishEndTime = tr.startTime() - self._textProduct.debug_print("final phishStartTime = %s final phishEndTime = %s" % + + # We found a new window, save it, reset and look for any additional windows + self._textProduct.debug_print("Found a new window:", 1) + self._textProduct.debug_print("window phishStartTime = %s window phishEndTime = %s" % (str(phishStartTime), str(phishEndTime)), 1) - break + + windows.append((phishStartTime, phishEndTime)) + phishStartTime = None + phishEndTime = None + + self._textProduct.debug_print("Looking for additional windows", 1) - self._textProduct.debug_print("new phishStartTime = %s new phishEndTime = %s" % + self._textProduct.debug_print("new phishStartTime = %s new phishEndTime = %s" % (str(phishStartTime), str(phishEndTime)), 1) + # Check for the case where a window doesn't end + if (phishStartTime is not None) and (phishEndTime is None): + self._textProduct.debug_print("Found a never-ending window:", 1) + self._textProduct.debug_print("window phishStartTime = %s window phishEndTime = %s" % + (str(phishStartTime), str(phishEndTime)), 1) + windows.append((phishStartTime, None)) + + # Create the final window + if len(windows) == 0: + phishStartTime = None + phishEndTime = None + else: + phishStartTime = windows[0][0] # Start time of first window + phishEndTime = windows[-1][1] # End time of last window + + self._textProduct.debug_print("Constructed the final window:", 1) + self._textProduct.debug_print("final phishStartTime = %s final phishEndTime = %s" % + (str(phishStartTime), str(phishEndTime)), 1) self._windowSurge = "Window of concern: "