import TextRules import SampleAnalysis import string, time, types import AFPS # # this product is formated for web dissemination # pqpf # james.frederick@noaa.gov # wfo tulsa class TextProduct(TextRules.TextRules, SampleAnalysis.SampleAnalysis): Definition = { "type": "smart", "displayName": "PQPF2", #"displayDialog": "None", "outputFile": "/data/local/GFE/PRODUCTS/PROBQPF.txt", # edit areas "defaultEditAreas" : "Combinations_AFM_TSA", # Name of map background for creating Combinations "mapNameForCombinations": "Zones_TSA", # Fcst or Official "database" : "Fcst", # textdb names "textdbPil": "PROBQPF", # Product ID for storing to AWIPS text database. "awipsWANPil": "KTSAPROBQPF", # Product ID for transmitting to AWIPS WAN. # Identifiers for product "fullStationID": "KTSA", # full station identifier (4letter) "wfoCityState": "TULSA OKLAHOMA", # city,state of wfo for header "wmoID": "XXXX00", # WMO ID code "pil": "PROBQPF", # product pil "productDescription": "EASTERN OKLAHOMA/NORTHWEST ARKANSAS PROBABILISTIC QPF", "legendText": "POP 6HR.......6-HOUR PROBABILITY OF MEASURABLE PRECIPITATION (0.01 INCH OR MORE)\n" + \ "QPF 6HR ......6-HOUR UNCONDITIONAL LIQUID EQUIVALENT PRECIPITATION AMOUNT (INCHES)\n" + \ "X 0.10 .......PROBABILITY OF LIQUID EQUIVALENT PRECIPITATION EXCEEDING 0.10 INCH (PERCENT).\n" + \ "X 0.50 .......PROBABILITY OF LIQUID EQUIVALENT PRECIPITATION EXCEEDING 0.50 INCH (PERCENT).\n" + \ "X 1.00 .......PROBABILITY OF LIQUID EQUIVALENT PRECIPITATION EXCEEDING 1.00 INCH (PERCENT).\n" + \ "X 2.00 .......PROBABILITY OF LIQUID EQUIVALENT PRECIPITATION EXCEEDING 2.00 INCH (PERCENT).\n" , # shouldn't have to edit anything below here "debug": 0, "runTimeEditAreas" : "no", # if yes, ask user at run time "lineLength": 66, "awipsTEXTDBhost": None, # textdb cmd host, None for local "periodCombining" : 0, # If 1, combine periods, if possible # automatic functions "autoSend": 0, #set to 1 to automatically transmit product "autoSendAddress": "000", #transmission address "autoStore": 0, #set to 1 to automatically store product in textDB "autoWrite": 1, #set to 1 to automatically write product to file ## Edit Areas: # Area Dictionary -- Descriptive information about zones "areaDictionary": "AreaDictionary", # Product-specific variables: "tempRangeThreshold": 5, # determines range vs. single value output "qpfRangeThreshold": 0.05, # determines range vs. single value output "snowRangeThreshold": 3, # determines range vs. single value output # Options for product "includeSnowAmt": 1, # set to 1 to include snow amount in output "includeHeatIndex": 0, # set to 1 to include heat index in output "includeWindChill": 0, # set to 1 to include wind chill in output "windChillDifference": 5, # T-WindChill difference before reporting "windChillLimit": 40, # don't report wind chills above this value "heatIndexDifference": 0, # indicates HI-T difference before reporting "heatIndexLimit": 80, # don't report heat index below this value "productType": "RDF", # Denotes product type, "RDF", or "PFM" } def __init__(self): TextRules.TextRules.__init__(self) SampleAnalysis.SampleAnalysis.__init__(self) def generateForecast(self, argDict): # Generate formatted product for a list of edit areas # Get variables from varDict and Definition self._getVariables(argDict) # Get the areaList -- derived fromEditAreas and # may be solicited at run-time from user if desired self._areaList = self.getAreaList(argDict) # Determine time ranges self._determineTimeRanges(argDict) # Sample the data self._sampleData(argDict) # Initialize the output string fcst = "" fcst = self._preProcessProduct(fcst, argDict) # Generate the product for each edit area in the list fraction = 0 fractionOne = 1.0/float(len(self._areaList)) percent = 50.0 for editArea, areaLabel in self._areaList: self.progressMessage(fraction, percent, "Making Product for " + areaLabel) fcst = self._preProcessArea(fcst, editArea, areaLabel, argDict) fcst = self._makeProduct(fcst, editArea, areaLabel, argDict) fcst = self._postProcessArea(fcst, editArea, areaLabel, argDict) fraction = fractionOne fcst = self._postProcessProduct(fcst, argDict) return fcst def _getVariables(self, argDict): # Make argDict accessible self.__argDict = argDict self._definition = argDict["forecastDef"] for key in self._definition.keys(): exec "self._" + key + "= self._definition[key]" # Basic widths for product self._rowLabelWidth = 10 #width of row label self._top6hrWidth = 6 #top part of product, 6hrly width self._top3hrWidth = 3 # Determine issue time self._issueTime = self.IFP().AbsTime.current() # Determine expiration time self._expirationTimeOffset = 12 self._expireTime = self._issueTime + self._expirationTimeOffset*3600 def _determineTimeRanges(self, argDict): # Determine time ranges for product - fairly complicated since # get current time self._currentTime = time.time() # Set up the beginning Time Range - note they are different for the tr6=12 # make time range in z time topTimeRange6hr = self.createTimeRange(tr6, tr6+1, "Zulu") ################################## # Special time considerations for UPDATE. If updating after midnight # local time for the PM package, then back off a day for # all time ranges, since the time ranges will change days at midnight ################################## print time.localtime(self._currentTime)[3] print topTimeRange6hr print self._backupOneDay(topTimeRange6hr) if time.localtime(self._currentTime)[3] < 4 or time.localtime(self._currentTime)[3] > 18 : topTimeRange6hr = self._backupOneDay(topTimeRange6hr) ################################## # Set up 3hr, 6hr, and 12hr elements in top portion ################################## timePeriod = 3 numPeriods = 56 timeSpan = 1 # for time labels self._topPeriods_3hr_snap = self.getPeriods(topTimeRange6hr, timePeriod, timeSpan, numPeriods, self._hour24localLabel) timePeriod = 6 timeSpan = 6 numPeriods = 28 self._topPeriods_6hr = self.getPeriods(topTimeRange6hr, timePeriod, timeSpan, numPeriods) ################################## # Setup Time Labels ################################## # Sets up the product's time labels hhmmTime = time.strftime("%d%H%M", time.gmtime(self._currentTime)) # Sets up the expiration time self._ddhhmmTime= time.strftime("%02d2200",time.gmtime(self._currentTime)) # timeLabel is the spelled-out version of the current time self._timeLabel = self.getCurrentTime(argDict, "%l%M %p %Z %a %b %e %Y", stripLeading=1).upper() return def _sampleData(self, argDict): # Sample the data. Sets up self._sampler sampleList = [] #sampleList.append((self._analysisList_3hr_top(),self._topPeriods_3hr)) sampleList.append((self._analysisList_6hr_top(),self._topPeriods_6hr)) sampleInfo = [] for analList, periods in sampleList: sampleInfo.append((analList, periods, self._areaList)) self._sampler = self.getSampler(argDict, sampleInfo) return def _preProcessProduct(self, fcst, argDict): # Add product heading to fcst string issuanceType = "" fcst = fcst + self._productDescription + issuanceType + "\n" fcst = fcst + "NATIONAL WEATHER SERVICE " fcst = fcst + self._wfoCityState +"\n" fcst = fcst + self._timeLabel + "\n\n" return fcst def _preProcessArea(self, fcst, editArea, areaLabel, argDict): areaHeader = self.makeAreaHeader( argDict, areaLabel, self._issueTime, self._expireTime, self._areaDictionary, self._defaultEditAreas) fcst = fcst + string.upper(areaHeader) return fcst def _makeProduct(self, fcst, editArea, areaLabel, argDict): ############################################################### # TOP PART OF PRODUCT - valid current time out to around 60hr) ############################################################### # Day, Period Label (UTC), Period Label (LT) dateLabel, utcLabel, ltLabel = self._calcPeriodLabels(\ self._topPeriods_3hr_snap, self._top3hrWidth, self._rowLabelWidth, 3) fcst = fcst + dateLabel + "\n" + ltLabel + "\n\n" # Create statLists statList_6hr = self.getStatList( self._sampler, self._analysisList_6hr_top(), self._topPeriods_6hr, editArea) # Pop fcst=fcst+ self.makeRow( "POP 6HR", self._top6hrWidth, self._topPeriods_6hr, statList_6hr, self._popValue, [], self._rowLabelWidth) # QPF fcst=fcst+ self.makeRow( "QPF 6HR", self._top6hrWidth, self._topPeriods_6hr, statList_6hr, self._qpfValue, [], self._rowLabelWidth) #pqpf fcst = fcst + self.makeRow( "X 0.10", self._top6hrWidth, self._topPeriods_6hr, statList_6hr, self._maxPoQ010, [],self._rowLabelWidth) fcst = fcst + self.makeRow( "X 0.50", self._top6hrWidth, self._topPeriods_6hr, statList_6hr, self._maxPoQ050, [],self._rowLabelWidth) fcst = fcst + self.makeRow( "X 1.00", self._top6hrWidth, self._topPeriods_6hr, statList_6hr, self._maxPoQ100, [],self._rowLabelWidth) fcst = fcst + self.makeRow( "X 2.00", self._top6hrWidth, self._topPeriods_6hr, statList_6hr, self._maxPoQ200, [],self._rowLabelWidth) return fcst def _postProcessArea(self, fcst, editArea, areaLabel, argDict): return fcst + "&&\n\n" + self._legendText + "\n\n$$\n" def _postProcessProduct(self, fcst, argDict): fcst = string.replace(fcst,"%expireTime",self._ddhhmmTime) #self.setProgressPercentage(100) #self.progressMessage(0, 100, self._displayName + " Complete") return fcst ######################################################################## # PRODUCT-SPECIFIC METHODS ######################################################################## def _analysisList_3hr_top(self): #None needed return [ ] def _analysisList_3hr_top_snap(self): return [ ## ("T", self.avg), ## ("Wind", self.vectorAvg), ## ("WindGust", self.minMax), ## ("WindChill", self.avg), ## ("HeatIndex", self.avg), ## ("Sky", self.avg), ## ("Td", self.avg), ## ("Wx", self.dominantWx), ## ("MixHgt", self.avg), ## ("Stability",self.avg), ## ("T1700Mix",self.minMax), ## ("TransWind", self.vectorAvg), ## ("VentRate",self.minMax), ] def _analysisList_6hr_top(self): return [ ## ("HeatIndex", self.minMax), ## ("WindChill", self.minMax), ("PoQ010", self.minMax), ("PoQ050", self.minMax), ("PoQ100", self.minMax), ("PoQ200", self.minMax), ("PoP",self.stdDevMaxAvg), ("QPF",self.minMaxSum), ] def _analysisList_12hr_top(self): return [ ("PoP",self.stdDevMaxAvg), ("QPF",self.minMaxSum), ## ("SnowAmt", self.minMaxSum), ## ("MaxT", self.minMaxAvg), ## ("MinT", self.minMaxAvg), ] def _hour24zuluLabel(self, timeRange): # returns the starting time of the timeRange in zulu, such as "03" label = timeRange.startTime().stringFmt("%H") return string.rjust(label, self._top3hrWidth) def _hour24localLabel(self, timeRange): # returns the starting time of the timeRange in localtime, such as "06" start = timeRange.startTime() + self.determineShift() label = start.stringFmt("%H") return string.rjust(label, self._top3hrWidth) def _tempValue(self, statDict, timeRange, argList): # return a string for the temperatures, such as "85" # can return MM for no data, blanks if timeRange is earlier than now val = self.getStats(statDict, argList[0]) if val is None: return "MM" return `int(round(val))` def _popValue(self, statDict, timeRange, argList): # return a string for the pop, such as "80" # PoP is rounded to nearest 10%, plus the 5% single value is allowed # can return MM for no data, blanks if timeRange is earlier than now #if timeRange.endTime().unixTime() < self._currentTime: # return "" val = self.getStats(statDict, "PoP__stdDevMaxAvg") if val is None: return "0" popMax5=int(self.round(val,"Nearest",1)) if popMax5 == 5: return "5" popMax10=int(self.round(val,"Nearest",1)) return `int(popMax10)` def _qpfValue(self, statDict, timeRange, argList): # Return a string for the QPF, such as 0, 0.05, or 0.25-0.49 # can return "MM" for missing data, # blanks if timeRange earlier than now #if timeRange.endTime().unixTime() < self._currentTime: # return "" val = self.getStats(statDict, "QPF__minMaxSum") if val is None: return "0" minV, maxV, sumV = val if maxV < 0.01: return "0" else: return string.strip("%5.2f" %maxV) def _snowValue(self, statDict, timeRange, argList): # Return a string for the Snow, such as 00-00, 5, or 5-9 # Can return "MM" for missing data, blanks if timeRange # earlier than now, or if greater than 36 hrs from the base time. #if timeRange.endTime().unixTime() < self._currentTime: # return "" basetime = ((self._topPeriods_3hr_snap[0])[0]).startTime().unixTime() if timeRange.startTime().unixTime() >= basetime + 36 * 3600: return "" val = self.getStats(statDict, "SnowAmt_minMaxSum") if val is None: return "MM" minV, maxV, sumV = val if maxV - minV > self._snowRangeThreshold: minString = `int(round(minV))` maxString = `int(round(maxV))` return minString+"-"+maxString elif sumV < 0.1: return "00-00" elif sumV < 0.5: return "T" else: return `int(round(sumV))` return "" def _maxPoQ010(self, statDict, timeRange, argList): # Returns string for HeatIndex, such as "85" # Returns "" for missing data, blanks if data earlier than now. #if timeRange.endTime().unixTime() < self._currentTime: # return "" val = self.getStats(statDict,"PoQ010__minMax") if val is None: return "0" minV, maxV = val if maxV >= 0: return `int(round(maxV))` return "0" def _maxPoQ050(self, statDict, timeRange, argList): # Returns string for HeatIndex, such as "85" # Returns "" for missing data, blanks if data earlier than now. #if timeRange.endTime().unixTime() < self._currentTime: # return "" val = self.getStats(statDict,"PoQ050__minMax") if val is None: return "0" minV, maxV = val if maxV >= 0: return `int(round(maxV))` return "0" def _maxPoQ100(self, statDict, timeRange, argList): # Returns string for HeatIndex, such as "85" # Returns "" for missing data, blanks if data earlier than now. #if timeRange.endTime().unixTime() < self._currentTime: # return "" val = self.getStats(statDict,"PoQ100__minMax") if val is None: return "0" minV, maxV = val if maxV >= 0: return `int(round(maxV))` return "0" def _maxPoQ200(self, statDict, timeRange, argList): # Returns string for HeatIndex, such as "85" # Returns "" for missing data, blanks if data earlier than now. #if timeRange.endTime().unixTime() < self._currentTime: # return "" val = self.getStats(statDict,"PoQ200__minMax") if val is None: return "0" minV, maxV = val if maxV >= 0: return `int(round(maxV))` return "0" def _calcPeriodLabels(self, periods, colWidth, startPoint, intervalHours): # Calculate the period labels and returns as (date, utc, lt) strings #DATE THU 08/01/02 FRI 08/02/02 #UTC 3HRLY 09 12 15 18 21 00 03 06 09 12 15 18 21 00 03 06 #MDT 3HRLY 03 06 09 12 15 18 21 00 03 06 09 12 15 18 21 00 # determine the column widths colWidths = [] if type(colWidth) is types.ListType: colWidths = colWidth else: for p in periods: colWidths.append(colWidth) # calculate the zulu labels zuluLabels = [] for period,label in periods: zuluLabels.append(self._hour24zuluLabel(period)) # zulu string zulu = "UTC " + `intervalHours` + "HRLY " zulu = string.ljust(zulu, startPoint) for x in xrange(len(zuluLabels)): zulu = self.addColValue(zulu, zuluLabels[x], colWidths[x]) # date and LT string (beginning) dateS = string.ljust('DATE', startPoint) ltZone = time.strftime("%Z",time.localtime(time.time())) #lt = string.ljust(ltZone, 4) + `intervalHours` + "HRLY " lt = string.ljust(ltZone, 4) lt = string.ljust(lt, startPoint) timePeriod,firstLabel= periods[0] # remainder of DATE and LT strings for x in xrange(len(periods)): timePeriod, label = periods[x] #if 06h, then put in a date label aligned with the 06 index = string.find(label, firstLabel) if index != -1: nfill = len(lt) - len(dateS) -1 + colWidths[x] - index dateS = dateS + string.ljust(' ',nfill) dayTime = timePeriod.startTime() + self.determineShift() dString = string.upper(dayTime.stringFmt("%a %m/%d/%y")) # is there room to write date? colAvail = 0 for y in xrange(x+1,len(periods)): colAvail = colAvail + colWidths[y] if colAvail + 9 >= len(dString): #9 is to allow extension dateS = dateS + dString #if len(dateS) < 70: dateS = dateS + " \\" # add in local time string lt = self.addColValue(lt, label, colWidths[x]) return (dateS, zulu, lt) def _backupOneDay(self, tr): # Routine takes input time range, subtracts 1 day, and returns # the new time range. startT = AFPS.AbsTime(tr.startTime().unixTime() - 86400) endT = AFPS.AbsTime(tr.endTime().unixTime() - 86400) return AFPS.TimeRange(startT, endT) ######################################################################## # OVERRIDING THRESHOLDS AND VARIABLES ######################################################################## def getDefaultPercentage(self, parmName): return 5.0