1860 lines
75 KiB
Python
1860 lines
75 KiB
Python
# ----------------------------------------------------------------------------
|
|
# 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.
|
|
#
|
|
# Aviation_Finalize - Version 20181203
|
|
#
|
|
# Last Modified: 3 December 2018
|
|
#
|
|
# Author: lefebvre
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# 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"]
|
|
|
|
VariableList = []
|
|
|
|
import time
|
|
import SmartScript
|
|
import numpy as np
|
|
import copy
|
|
import tkinter as tk
|
|
import AbsTime, TimeRange
|
|
import Aviation_EDASConfig as FinalizeConfig
|
|
import Exceptions
|
|
|
|
# Translates words into wxTypes
|
|
# Used by the method addNewWxFromVis()
|
|
wxTypesDict = {"Fog" : "F",
|
|
"Haze" : "H",
|
|
"Smoke" : "K",
|
|
}
|
|
|
|
# Tooltip class so we can specify a tooltip for any widget. Just pass in the
|
|
# widget and the text to be displayed.
|
|
class CreateToolTip(object):
|
|
|
|
def __init__(self, widget, text='widget info'):
|
|
self.widget = widget
|
|
self.text = text
|
|
self.widget.bind("<Enter>", self.enter)
|
|
self.widget.bind("<Leave>", self.close)
|
|
|
|
def enter(self, event=None):
|
|
x = y = 0
|
|
x, y, cx, cy = self.widget.bbox("insert")
|
|
x += self.widget.winfo_rootx() + 25
|
|
y += self.widget.winfo_rooty() + 20
|
|
# creates a toplevel window
|
|
self.tw = tk.Toplevel(self.widget)
|
|
# Leaves only the label and removes the app window
|
|
self.tw.wm_overrideredirect(True)
|
|
self.tw.wm_geometry("+%d+%d" % (x, y))
|
|
label = tk.Label(self.tw, text=self.text, justify='left',
|
|
background='yellow', relief='solid', borderwidth=1,
|
|
font=("times", "10", "normal"))
|
|
label.pack(ipadx=1)
|
|
|
|
def close(self, event=None):
|
|
if self.tw:
|
|
self.tw.destroy()
|
|
|
|
# End Tooltip class
|
|
|
|
class Procedure (SmartScript.SmartScript):
|
|
def __init__(self, dbss):
|
|
SmartScript.SmartScript.__init__(self, dbss)
|
|
|
|
# Fetches the inventory as a list of timeRanges of the specified model and
|
|
# and weather element. This list will be restricted to the specified timeRange,
|
|
# if it's provided.
|
|
def getWEInventory(self, modelName, WEName, timeRange=None):
|
|
|
|
allTimes = TimeRange.allTimes()
|
|
|
|
if timeRange is None:
|
|
timeRange = allTimes
|
|
|
|
trList = []
|
|
# getGridInfo will just die if the modelName or weName is not valid
|
|
# so wrap it in a try block and return [] if it fails
|
|
try:
|
|
gridInfo = self.getGridInfo(modelName, WEName, "SFC", timeRange)
|
|
except Exceptions.EditActionError:
|
|
return trList
|
|
|
|
for g in gridInfo:
|
|
if timeRange.overlaps(g.gridTime()):
|
|
trList.append(g.gridTime())
|
|
|
|
return trList
|
|
# Returns True if the specified WeName is a rate parm.
|
|
def cumulativeParm(self, weName, dbName):
|
|
parm = self.getParm(dbName, weName, "SFC")
|
|
if parm is None:
|
|
return False
|
|
|
|
rateParm = parm.getGridInfo().isRateParm()
|
|
rateParm = (rateParm == 1) # convert int to boolean
|
|
|
|
return rateParm
|
|
|
|
# Get the type of the specified weather element name
|
|
def getWEType(self, weName):
|
|
|
|
parm = self.getParm("Fcst", weName, "SFC")
|
|
# Check for None in case the element is not configured.
|
|
if parm is None:
|
|
return None
|
|
|
|
parmType = str(parm.getGridInfo().getGridType())
|
|
|
|
return parmType
|
|
|
|
def getParmMinMaxLimits(self, modelName, weName):
|
|
|
|
parm = self.getParm(modelName, weName, "SFC")
|
|
|
|
return parm.getGridInfo().getMinValue(), parm.getGridInfo().getMaxValue()
|
|
|
|
# Creates a list of hourly timeRanges based on the specified timeRange
|
|
def getTimeRanges(self, timeRange):
|
|
|
|
start = timeRange.startTime().unixTime()
|
|
end = timeRange.endTime().unixTime()
|
|
|
|
trList = []
|
|
for t in range(start, end, 3600):
|
|
s = AbsTime.AbsTime(t)
|
|
e = AbsTime.AbsTime(t + 3600)
|
|
tr = TimeRange.TimeRange(s, e)
|
|
|
|
trList.append(tr)
|
|
|
|
return trList
|
|
|
|
# Fetches every grid within the specified timeRange for the elements in weList
|
|
def fetchAllGrids(self, weList, selectedTR):
|
|
|
|
weDict = {}
|
|
emptyMask = self.empty(np.bool)
|
|
for weName in weList:
|
|
trList = self.getWEInventory("Fcst", weName, selectedTR)
|
|
weDict[weName] = {}
|
|
|
|
cumParm = self.cumulativeParm(weName, "Fcst")
|
|
|
|
for tr in trList:
|
|
|
|
grid = self.getGrids("Fcst", weName, "SFC", tr)
|
|
if tr.duration() == 3600:
|
|
weDict[weName][tr] = grid, emptyMask
|
|
continue
|
|
|
|
# Parm of different duration needs to be split
|
|
hourlyTRList = self.getTimeRanges(tr)
|
|
# If it's a cumulative parm divide by the duration
|
|
if cumParm:
|
|
grid = grid / (tr.duration() // 3600)
|
|
|
|
for hourlyTR in hourlyTRList:
|
|
weDict[weName][hourlyTR] = grid, emptyMask
|
|
|
|
return weDict
|
|
|
|
# Returns a mask where the Wx grid matches the specified Wx type and intensity.
|
|
# if wxIntens is None, the mask matches the Wx type and all intensities.
|
|
def getWxMask(self, wxGrid, wxType, wxIntens=None):
|
|
|
|
byteGrid, wxKeys = wxGrid
|
|
|
|
gridMask = np.zeros(byteGrid.shape, np.bool)
|
|
for wxKey in wxKeys:
|
|
subKeys = wxKey.split("^")
|
|
for subKey in subKeys:
|
|
subParts = subKey.split(":")
|
|
if subParts[1] == wxType and subParts[2] == wxIntens or \
|
|
subParts[1] == wxType and wxIntens is None:
|
|
wxIndex = self.getIndex(wxKey, wxKeys)
|
|
gridMask = gridMask | (byteGrid == wxIndex)
|
|
|
|
return gridMask
|
|
|
|
# Returns the three Wx components if both type are obstructions and "" otherwise
|
|
def bothTypesObstructions(self, newSubType, wxKey):
|
|
|
|
subKeys = wxKey.split("^")
|
|
for subKey in subKeys:
|
|
subCov, subType, subInten = self.parseCovTypeIntens(subKey)
|
|
# if both are obstructions, return True
|
|
if subType in self._obstructionTypes and newSubType in self._obstructionTypes:
|
|
return subType
|
|
|
|
return ""
|
|
|
|
|
|
# Called when we want to replace a subkey within and existing wx key
|
|
def replaceWxSubKey(self, wxKey, wxTypeToReplace, newWxCov, newWxType, newWxIntens):
|
|
newWxKey = ""
|
|
|
|
subKeys = wxKey.split("^")
|
|
for subKey in subKeys:
|
|
wxCov, wxType, wxIntens = self.parseCovTypeIntens(subKey)
|
|
|
|
# Add the separator if needed
|
|
if newWxKey != "":
|
|
newWxKey = newWxKey + "^"
|
|
|
|
if wxType == wxTypeToReplace: # Replace this subKey
|
|
newWxKey = newWxKey + newWxCov + ":" + newWxType + ":" + newWxIntens + ":<NoVis>:"
|
|
else: # Add to the existing key
|
|
newWxKey = newWxKey + subKey
|
|
|
|
return newWxKey
|
|
|
|
def removeWxType(self, wxGrid, removeWxType):
|
|
|
|
byteGrid, wxKeys = wxGrid
|
|
initialWxKeys = copy.copy(wxKeys)
|
|
noWxUglyStr = "<NoCov>:<NoWx>:<NoInten>:<NoVis>:"
|
|
|
|
for wxKey in initialWxKeys:
|
|
newKey = ""
|
|
|
|
subKeys = wxKey.split("^")
|
|
|
|
for subKey in subKeys:
|
|
wxCov, wxType, wxInten = self.parseCovTypeIntens(subKey)
|
|
|
|
if wxType != removeWxType:
|
|
if newKey == "":
|
|
newKey = subKey
|
|
else:
|
|
newKey = newKey + "^" + subKey
|
|
|
|
# Don't bother changing the WxGrid, if the key is the same
|
|
if newKey == wxKey:
|
|
continue
|
|
# If the key was only the specified wxType the newKey will be empty
|
|
if newKey == "":
|
|
newKey = noWxUglyStr
|
|
|
|
oldIndex = self.getIndex(wxKey, wxKeys)
|
|
mask = byteGrid == oldIndex
|
|
newIndex = self.getIndex(newKey, wxKeys)
|
|
byteGrid[mask] = newIndex
|
|
|
|
return byteGrid, wxKeys
|
|
|
|
|
|
# Adds the new Wx coverage, type, and intensity to the specified wx grid and keys.
|
|
# If the wx type is the same as any type found in the wxKeys, the type is preserved
|
|
# and the specified coverage and intensity is used.
|
|
def addNewWx(self, wxGrid, wxKeys, newWxCov, newWxType, newWxIntens, mask):
|
|
|
|
initialWxKeys = copy.copy(wxKeys)
|
|
|
|
for wxKey in initialWxKeys:
|
|
newType = newWxType
|
|
|
|
wxIndex = self.getIndex(wxKey, wxKeys)
|
|
wxMask = wxGrid == wxIndex
|
|
|
|
# See if there's any overlap and if not skip this wxKey
|
|
overlap = wxMask & mask
|
|
if not overlap.any():
|
|
continue
|
|
|
|
# Check to see if we're trying to add the same Wx type.
|
|
# If so, build a brand new key and add that to the Wx keys
|
|
# Trying to combine two keys of the same type is not allowed
|
|
wxParts = self.wxCovTypeIntensMatchType(wxKey, newType) # Returns None if not the same wx type
|
|
if wxParts is not None: # We found matching wxTypes somewhere in the full key
|
|
if len(wxKey.split("^")) == 1: # only one subKey
|
|
# Make a brand new subkey
|
|
newWxKey = newWxCov + ":" + newType + ":" + newWxIntens + ":<NoVis>:"
|
|
else: # This wxKey has more subKeys
|
|
# Make a new key that includes the key we want plus the other subKeys
|
|
subKeyList = wxKey.split("^")
|
|
newWxKey = ""
|
|
for subKey in subKeyList:
|
|
subCov, subType, subInten = self.parseCovTypeIntens(subKey)
|
|
if self.wxTypeMatches(subType, newType):
|
|
if newWxKey != "": # Add the leading caret
|
|
newWxKey = newWxKey + "^"
|
|
newWxKey = newWxKey + newWxCov + ":" + newType + ":" + newWxIntens + ":<NoVis>:"
|
|
else:
|
|
newWxKey = newWxKey + "^" + subKey
|
|
|
|
else: # No wxType match, so make a new key
|
|
# If both types are obstructions, replace whatever is there
|
|
replaceWxType = self.bothTypesObstructions(newWxType, wxKey)
|
|
if replaceWxType != "":
|
|
newWxKey = self.replaceWxSubKey(wxKey, replaceWxType, newWxCov, newWxType, newWxIntens)
|
|
#print "replacing:", wxKey, "with:", newWxCov, newWxType, newWxIntens
|
|
else: # just add it in to the existing key
|
|
newWxKey = self.makeNewWxKey(wxKey, newWxCov, newType, newWxIntens)
|
|
|
|
newWxIndex = self.getIndex(newWxKey, wxKeys)
|
|
wxGrid[overlap] = newWxIndex
|
|
|
|
return wxGrid, wxKeys
|
|
|
|
# Makes and returns a new Wx sub key based on the specified old key and new Wx components.
|
|
# Old and new weather types should not match.
|
|
def makeNewWxKey(self, oldKey, newCov, newType, newIntens):
|
|
|
|
keyToAdd = newCov + ":" + newType + ":" + newIntens + ":<NoVis>:"
|
|
|
|
if oldKey == self._noWxStr:
|
|
return keyToAdd
|
|
|
|
newKey = ""
|
|
|
|
# See if a subKey already has the F or ZF and replace it with the new attributes
|
|
subKeys = oldKey.split("^")
|
|
for subKey in subKeys:
|
|
wxCov, wxType, wxIntens = self.parseCovTypeIntens(subKey)
|
|
if wxCov == "":
|
|
continue
|
|
|
|
# If old type is F and we want FZ or vice versa, replace the subKey
|
|
elif wxType in ["F", "ZF"] and newType in ["F", "ZF"]:
|
|
keyPart = keyToAdd
|
|
else:
|
|
keyPart = subKey + "^" + keyToAdd
|
|
|
|
if newKey == "":
|
|
newKey = newKey + keyPart
|
|
else:
|
|
newKey = newKey + "^" + keyPart
|
|
|
|
return newKey
|
|
|
|
# Caps the CBP height based on different ranges of PoP values
|
|
def popAssignsCBP(self, PoP, CloudBasePrimary, Sky, Visibility):
|
|
changeMask = self.empty(np.bool)
|
|
mask = (PoP >= 15.0) & (PoP < 25.0) & (CloudBasePrimary > 110.0) & self._editAreaMask
|
|
CloudBasePrimary[mask] = 110.0 # 11000 feet
|
|
changeMask = changeMask | mask
|
|
mask = (PoP >= 25.0) & (PoP < 55.0) & (CloudBasePrimary > 100.0)
|
|
CloudBasePrimary[mask] = 100.0 # 10000 feet
|
|
changeMask = changeMask | mask
|
|
mask = (PoP >= 55.0) & (CloudBasePrimary > 90.0)
|
|
CloudBasePrimary[mask] = 90.0 # 9000 feet
|
|
changeMask = changeMask | mask
|
|
|
|
# Make sure we have no "zero" ceilings if SKy < OVC and good Visibility
|
|
mask = (Sky <= 87.5) & (Visibility >= 7.0) & (CloudBasePrimary < 1.0)
|
|
CloudBasePrimary[mask] = 1.0
|
|
changeMask = changeMask | mask
|
|
|
|
return CloudBasePrimary, changeMask
|
|
|
|
# Returns a mask where any precipitation type is found.
|
|
def getPrecipNonPrecipMask(self, Wx, wxTypeList):
|
|
|
|
mask = self.empty(np.bool) # mask of zeros
|
|
wxGrid, wxKeys = Wx
|
|
# Check each key
|
|
for wxKey in wxKeys:
|
|
# and each subkey within each key
|
|
subKeys = wxKey.split("^")
|
|
for subKey in subKeys:
|
|
wxCov, wxType, wxIntens = self.parseCovTypeIntens(subKey)
|
|
|
|
if wxType in wxTypeList:
|
|
wxIndex = self.getIndex(wxKey, wxKeys)
|
|
mask = mask | (wxGrid == wxIndex)
|
|
|
|
return mask
|
|
|
|
# Rule that ensures the Visibility remains consistent with the Wx and PoP.
|
|
def lowPoPNoWxVis10(self, PoP, Wx, Visibility):
|
|
changeMask = self.empty(np.bool)
|
|
|
|
# Get the mask of places where there are obstructions.
|
|
obstructionMask = self.getPrecipNonPrecipMask(Wx, self._obstructionTypes)
|
|
# But we really want the place where there is no precip.
|
|
noPrecipMask = ~obstructionMask
|
|
# Calculated the other masks
|
|
lowVisMask = Visibility < self._config["visPopThreshold"]
|
|
popMask = (PoP < 55.0) & self._editAreaMask
|
|
# Set the visibility
|
|
mask = popMask & noPrecipMask & lowVisMask
|
|
Visibility[mask] = self._config["visPopThreshold"]
|
|
changeMask = changeMask | mask
|
|
|
|
highPoPMask = PoP >= 55.0
|
|
visMask = Visibility > 6.0
|
|
|
|
# Set the visibility
|
|
mask = highPoPMask & visMask & self._editAreaMask
|
|
Visibility[mask] = 6.0
|
|
changeMask = changeMask | mask
|
|
|
|
return Visibility, changeMask
|
|
|
|
# Ensures that Sky is not less than the PoP value
|
|
def popAssignsSky(self, PoP, Sky):
|
|
changeMask = self.empty(np.bool)
|
|
mask = (PoP > Sky) & self._editAreaMask
|
|
Sky[mask] = PoP[mask]
|
|
changeMask = changeMask | mask
|
|
|
|
return Sky, changeMask
|
|
|
|
# Removes zero CBP values where Sky is not OVC and Visibility > 0.5
|
|
def noZeroCeiling(self, CloudBasePrimary, Sky, Visibility):
|
|
changeMask = self.empty(np.bool)
|
|
|
|
zeroCeilingMask = CloudBasePrimary == 0.0
|
|
noOVCMask = Sky < 93.5
|
|
visMask = Visibility >= 0.5
|
|
|
|
mask = zeroCeilingMask & noOVCMask & visMask
|
|
CloudBasePrimary[mask] = 1.0
|
|
changeMask = changeMask | mask
|
|
|
|
return CloudBasePrimary, changeMask
|
|
|
|
# Internal method to extract the Visibility limits based on wxType and coverage.
|
|
def calcCoverageLimits(self, wxType, coverage):
|
|
covList = copy.copy(self._wxTypeCovDict[wxType]) # need to copy so we don't mess with the original copy.
|
|
covList.reverse()
|
|
visList = [0.0]
|
|
for i in range(len(covList)):
|
|
visList.append(self._scales[covList[i]].get())
|
|
|
|
covIndex = covList.index(coverage)
|
|
visMin = visList[covIndex]
|
|
visMax = visList[covIndex + 1]
|
|
|
|
return visMin, visMax
|
|
|
|
|
|
# Modify PoTFog based on Visibility
|
|
def makePoTGridFromVis(self, Visibility, T, Td, wxType, TMinusTd, tr):
|
|
# Make masks of interesting places
|
|
tBelow32 = (T <= 32.0)
|
|
# Places where we want Fog
|
|
fogMask = ((T - Td) <= TMinusTd) & self._editAreaMask
|
|
|
|
covList = self._wxTypeCovDict[wxType] # Fetch coverages for Fog
|
|
|
|
covDict = {"Patchy" : 20.0,
|
|
"Areas" : 40.0,
|
|
"Wide" : 70.0,
|
|
"Def" : 100,
|
|
}
|
|
|
|
if wxType == "Fog":
|
|
potFogGrid = self.empty() # grid of zeros
|
|
potFreezingFogGrid = self.empty() # grid of zeros
|
|
for cov in covList:
|
|
covMin, covMax = self.calcCoverageLimits(wxType, cov)
|
|
|
|
mask = (Visibility >= covMin) & (Visibility < covMax) & fogMask
|
|
|
|
if not mask.any(): # skip if no point are found
|
|
continue
|
|
|
|
if cov not in covDict:
|
|
print("ERROR!! coverage:", cov, " not found in covDict")
|
|
continue
|
|
|
|
potValue = covDict[cov] # Fetch the potFog value from the dict
|
|
|
|
# Update the potFogGrid grid
|
|
potFogGrid[mask] = potValue
|
|
|
|
# Update the potFreezingFogGrid grid
|
|
potFreezingFogGrid[mask & tBelow32] = potValue
|
|
|
|
# Create the grids
|
|
self.createGrid("Fcst", "PotFog", "SCALAR", potFogGrid, tr)
|
|
self.createGrid("Fcst", "PotFreezingFog", "SCALAR", potFreezingFogGrid, tr)
|
|
|
|
# Add Haze
|
|
elif wxType == "Haze":
|
|
potHazeGrid = self.empty() # grid of zeros
|
|
for cov in covList:
|
|
covMin, covMax = self.calcCoverageLimits(wxType, cov)
|
|
mask = (Visibility >= covMin) & (Visibility < covMax) & self._editAreaMask
|
|
if not mask.any(): # skip if no point are found
|
|
continue
|
|
|
|
if cov not in covDict:
|
|
print("ERROR!! coverage:", cov, " not found in covDict")
|
|
continue
|
|
|
|
potValue = covDict[cov]
|
|
potHazeGrid[mask] = potValue
|
|
self.createGrid("Fcst", "PotHaze", "SCALAR", potHazeGrid, tr)
|
|
|
|
# Add Smoke
|
|
elif wxType == "Smoke":
|
|
potSmokeGrid = self.empty() # grid of zeros
|
|
for cov in covList:
|
|
covMin, covMax = self.calcCoverageLimits(wxType, cov)
|
|
mask = (Visibility >= covMin) & (Visibility < covMax) & self._editAreaMask
|
|
|
|
if not mask.any(): # skip if no point are found
|
|
continue
|
|
|
|
if cov not in covDict:
|
|
print("ERROR!! coverage:", cov, " not found in covDict")
|
|
continue
|
|
|
|
potValue = covDict[cov]
|
|
potSmokeGrid[mask] = potValue
|
|
self.createGrid("Fcst", "PotSmoke", "SCALAR", potSmokeGrid, tr)
|
|
|
|
return
|
|
|
|
# Add new weather (F, K, H) where Visibility values dictate.
|
|
def addNewWxFromVis(self, Visibility, T, Td, Wx, newWxType, TMinusTd, tr):
|
|
|
|
oldByteGrid, oldKeys = Wx
|
|
# First remove the weather from the grid we will be changing
|
|
# wxTypesDict is defined at the top of this module.
|
|
wxGrid, wxKeys = self.removeWxType(Wx, wxTypesDict[newWxType])
|
|
|
|
changeMask = self.empty(np.bool)
|
|
|
|
noWxIntensity = "<NoInten>"
|
|
# Make masks of interesting places
|
|
visBelowPoint25 = Visibility <= 0.25
|
|
visBelowFiveEighths = Visibility < 0.625
|
|
tBelow32 = T <= 32.0
|
|
tAbove32 = T > 32.0
|
|
|
|
# Define the criteria for Obstruction Wx based on the above masks
|
|
fogMask = ((T - Td) <= TMinusTd) & self._editAreaMask
|
|
|
|
covList = self._wxTypeCovDict[newWxType]
|
|
# Add Fog to Wx
|
|
if newWxType == "Fog":
|
|
for cov in covList:
|
|
covMin, covMax = self.calcCoverageLimits(newWxType, cov)
|
|
mask = (Visibility >= covMin) & (Visibility <= covMax) & fogMask
|
|
changeMask = changeMask | mask
|
|
|
|
# Add Fog
|
|
newWxGrid, newWxKeys = self.addNewWx(wxGrid, wxKeys, cov, "F", noWxIntensity, mask & tAbove32)
|
|
changeMask = changeMask | (mask & tAbove32)
|
|
|
|
# Add Freezing Fog - Use the Wx grid and keys from above.
|
|
newWxGrid, newWxKeys = self.addNewWx(newWxGrid, newWxKeys, cov, "ZF", noWxIntensity, mask & tBelow32 & visBelowFiveEighths)
|
|
changeMask = changeMask | (mask & tBelow32 & visBelowFiveEighths)
|
|
|
|
# Check for dense fog conditions
|
|
newWxGrid, newWxKeys = self.addNewWx(newWxGrid, newWxKeys, cov, "F", "+", mask & tAbove32 & visBelowPoint25)
|
|
changeMask = changeMask | (mask & tAbove32 & visBelowPoint25)
|
|
|
|
# Check for freezing Dense Fog conditions
|
|
newWxGrid, newWxKeys = self.addNewWx(newWxGrid, newWxKeys, cov, "ZF", "+", mask & tBelow32 & visBelowPoint25)
|
|
changeMask = changeMask | (mask & tBelow32 & visBelowPoint25)
|
|
|
|
if self._config["MakePotFog"] == "Yes":
|
|
# Make the PoTFog grid
|
|
self.makePoTGridFromVis(Visibility, T, Td, newWxType, TMinusTd, tr)
|
|
|
|
# Add Haze to Wx
|
|
elif newWxType == "Haze": # Haze has only one coverage - "Def"
|
|
for cov in covList:
|
|
covMin, covMax = self.calcCoverageLimits(newWxType, cov)
|
|
mask = (Visibility >= covMin) & (Visibility < covMax) & self._editAreaMask
|
|
newWxGrid, newWxKeys = self.addNewWx(wxGrid, wxKeys, cov, "H", noWxIntensity, mask)
|
|
changeMask = changeMask | mask
|
|
|
|
if self._config["MakePotFog"] == "Yes":
|
|
# Make the PoTFog grid
|
|
self.makePoTGridFromVis(Visibility, T, Td, newWxType, TMinusTd, tr)
|
|
|
|
# Add Smoke to Wx
|
|
elif newWxType == "Smoke":
|
|
for cov in covList:
|
|
covMin, covMax = self.calcCoverageLimits(newWxType, cov)
|
|
mask = (Visibility >= covMin) & (Visibility < covMax) & self._editAreaMask
|
|
newWxGrid, newWxKeys = self.addNewWx(wxGrid, wxKeys, cov, "K", noWxIntensity, mask)
|
|
changeMask = changeMask | mask
|
|
|
|
if self._config["MakePotFog"] == "Yes":
|
|
# Make the PoTFog grid
|
|
self.makePoTGridFromVis(Visibility, T, Td, newWxType, TMinusTd, tr)
|
|
|
|
return (newWxGrid, newWxKeys), changeMask
|
|
|
|
# Modifies the Visibility base on the Wx coverage and intensity.
|
|
def updateVisFromWx(self, Wx, Visibility):
|
|
changeMask = self.empty(np.bool)
|
|
|
|
# Set Vis to 7SM where <7 and NoWx
|
|
wxMask = self.getWxMask(Wx, "<NoWx>", "<NoInten>")
|
|
visMask = (Visibility < 7.0) & self._editAreaMask
|
|
Visibility[wxMask & visMask] = 7.0
|
|
changeMask = changeMask | (wxMask & visMask)
|
|
|
|
# Set Vis to 6 if regular F
|
|
wxMask = self.getWxMask(Wx, "F", "<NoInten>")
|
|
visMask = (Visibility > 6.0) & self._editAreaMask
|
|
Visibility[wxMask & visMask] = 6.0
|
|
changeMask = changeMask | (wxMask & visMask)
|
|
|
|
# Set Vis to 0.25 if F+
|
|
wxMask = self.getWxMask(Wx, "F", "+")
|
|
visMask = (Visibility > 0.25) & self._editAreaMask
|
|
Visibility[wxMask & visMask] = 0.25
|
|
changeMask = changeMask | (wxMask & visMask)
|
|
|
|
# Set Vis based on Snow intensity
|
|
wxMask = self.getWxMask(Wx, "S", "m")
|
|
visMask = (Visibility > 0.5) & self._editAreaMask
|
|
Visibility[wxMask & visMask] = 0.5
|
|
changeMask = changeMask | (wxMask & visMask)
|
|
|
|
wxMask = self.getWxMask(Wx, "S", "+")
|
|
visMask = (Visibility > 0.25) & self._editAreaMask
|
|
Visibility[wxMask & visMask] = 0.25
|
|
changeMask = changeMask | (wxMask & visMask)
|
|
|
|
# Make masks and set the Vis to 6.0 for random wxTypes. Add more to this list
|
|
# in order to have them considered.
|
|
wxTypes = ["K"] # make this a list for now in case new type need to be added later
|
|
for wxType in wxTypes:
|
|
wxMask = self.getWxMask(Wx, wxType, "<NoInten>")
|
|
visMask = (Visibility > 6.0) & self._editAreaMask
|
|
Visibility[wxMask & visMask] = 6.0
|
|
changeMask = changeMask | (wxMask & visMask)
|
|
|
|
return Visibility, changeMask
|
|
|
|
# Simple method to parse a Wx string
|
|
def parseCovTypeIntens(self, wxSubKey):
|
|
keyParts = wxSubKey.split(":")
|
|
# Just return the first three coverage, type, and intensity
|
|
return keyParts[0:3]
|
|
|
|
# Returns the list of weather types in the proper order based on configuration.
|
|
def getWxTypeOrder(self, wxKey):
|
|
|
|
# Get the choice from the GUI
|
|
wxCat = self._intVar.get()
|
|
|
|
if wxCat == "Precip":
|
|
wxTypeList = self._config["IntensPrecipList"] + \
|
|
self._config["IntensNonPrecipList"]
|
|
elif wxCat == "Non-Precip":
|
|
wxTypeList = self._config["IntensNonPrecipList"] + \
|
|
self._config["IntensPrecipList"]
|
|
else:
|
|
print("ERROR! Invalid Intensity choice from GUI!!!!!!")
|
|
|
|
return wxTypeList
|
|
|
|
# Determines which wx type to change based on configuration.
|
|
def determineWxTypeToAdjust(self, wxKey):
|
|
|
|
# Get the order of wx types based on GUI choices and configuration
|
|
wxTypeList = self.getWxTypeOrder(wxKey)
|
|
|
|
# Now find this wx type in the wxKey
|
|
subKeys = wxKey.split("^")
|
|
for wxType in wxTypeList:
|
|
for subKey in subKeys:
|
|
subKeyWxCov, subKeyWxType, subKeyWxInt = self.parseCovTypeIntens(subKey)
|
|
if wxType == subKeyWxType:
|
|
return wxType
|
|
|
|
# If we didn't find a matching wx type, return None
|
|
return None
|
|
|
|
# Returns True if the weather types match. Snow and snow showers are treated
|
|
# as a match as are rain and rain showers.
|
|
def wxTypeMatches(self, wxType1, wxType2):
|
|
|
|
if wxType1 == wxType2:
|
|
return True
|
|
elif wxType1 in ["S", "SW"] and wxType2 in ["S", "SW"]:
|
|
return True
|
|
elif wxType1 in ["R", "RW"] and wxType2 in ["R", "RW"]:
|
|
return True
|
|
|
|
return False
|
|
|
|
# Returns the wx coverage, type, and intensity if the type matches wxType.
|
|
# Otherwise returns None
|
|
def wxCovTypeIntensMatchType(self, wxKey, wxType):
|
|
|
|
parts = wxKey.split("^")
|
|
for p in parts:
|
|
coverage, wxKeyType, intensity = self.parseCovTypeIntens(wxKey)
|
|
|
|
if self.wxTypeMatches(wxKeyType, wxType):
|
|
return coverage, wxKeyType, intensity
|
|
|
|
return None
|
|
|
|
# Updates the Wx Intensity grid based on visibility.
|
|
def updateWxIntensWithVis(self, Visibility, Wx):
|
|
changeMask = self.empty(np.bool)
|
|
|
|
# The intensities of these Wx types only are allowed to be updated
|
|
wxTypes = ["S", "SW", "L", "ZL", "F", "ZF"]
|
|
# Get the area masks based on Vis
|
|
visHeavy = Visibility <= 0.25
|
|
visMod = (Visibility > 0.25) & (Visibility <= 0.5)
|
|
visLight = Visibility > 0.5
|
|
# Make a list of the intensity to add and mask that goes with those conditions
|
|
maskInfo = [("m", visMod), ("+", visHeavy), ("-", visLight)]
|
|
|
|
wxGrid, wxKeys = Wx
|
|
|
|
# Make a copy of the keys so they won't get modified
|
|
# If we don't do this, an infinite loop occurs.
|
|
initWxKeys = copy.copy(wxKeys)
|
|
|
|
for wxKey in initWxKeys:
|
|
|
|
if wxKey == self._noWxStr:
|
|
continue
|
|
|
|
# Adjust the intensity of a Wx type determined by the configuration
|
|
# and the choice on the GUI. Wx type adjusted depends on the other
|
|
# Wx sub keys.
|
|
targetWxType = self.determineWxTypeToAdjust(wxKey)
|
|
|
|
subKeys = wxKey.split("^")
|
|
|
|
# Examine each subKey
|
|
for subKey in subKeys:
|
|
subCov, subType, subIntens = self.parseCovTypeIntens(subKey)
|
|
|
|
# If it's not one of the special types, skip it
|
|
if subType not in wxTypes:
|
|
continue
|
|
# If it's not the type we're looking to adjust, skip it
|
|
if subType != targetWxType:
|
|
continue
|
|
|
|
# Now check each intensity to see if any changes are needed
|
|
for intens, visMask in maskInfo:
|
|
intenToAdd = intens
|
|
# If no points in the mask skip it.
|
|
if not visMask.any():
|
|
continue
|
|
# Fog intensity behaves differently
|
|
if targetWxType in ["F", "ZF"] and intens in ["-", "m"]:
|
|
intenToAdd = "<NoInten>"
|
|
|
|
if intens == subIntens: # this subKey already has the right intensity
|
|
continue
|
|
|
|
# At this point, we have the type and intensity that we're looking for
|
|
# so add/change to the new intensity
|
|
oldIndex = self.getIndex(wxKey, wxKeys)
|
|
mask = (wxGrid == oldIndex) & visMask & self._editAreaMask
|
|
|
|
# Skip if no points in the mask
|
|
if not mask.any():
|
|
continue
|
|
|
|
wxGrid, wxKeys = self.addNewWx(wxGrid, wxKeys, subCov, subType, intenToAdd, mask)
|
|
changeMask = changeMask | mask
|
|
|
|
return (wxGrid, wxKeys), changeMask
|
|
|
|
# Sets the WindGust to the WindSpeed value where gust < speed
|
|
def updateWindGustWithWind(self, Wind, WindGust):
|
|
changeMask = self.empty(np.bool)
|
|
|
|
speed, direction = Wind
|
|
mask = (speed > WindGust) & self._editAreaMask
|
|
WindGust[mask] = speed[mask]
|
|
changeMask = changeMask | mask
|
|
|
|
return WindGust, changeMask
|
|
|
|
# Adds the specified Vis to the specified Wx key.
|
|
def addVisToWxKey(self, oldKey, visStr):
|
|
# Don't add Vis to <NoWx>
|
|
if oldKey == self._noWxStr:
|
|
return oldKey
|
|
|
|
subKeys = oldKey.split("^")
|
|
newKey = ""
|
|
|
|
for i in range(0, len(subKeys)):
|
|
|
|
# if it's the first subKey, set the new Vis, otherwise set to <NoVis>
|
|
if i == 0:
|
|
newVis = visStr
|
|
else:
|
|
newVis = "<NoVis>"
|
|
|
|
# Split the subKey into components and reassemble
|
|
subParts = subKeys[i].split(":")
|
|
|
|
if len(subParts) >= 3:
|
|
newSubKey = subParts[0] + ":" + subParts[1] + ":" + subParts[2] + ":" + newVis + ":"
|
|
|
|
if len(subParts) == 5 and subParts[4] != "":
|
|
newSubKey = newSubKey + subParts[4] + ":"
|
|
|
|
# Append to the new subKey
|
|
newKey = newKey + newSubKey + "^"
|
|
|
|
# strip off the last "^"
|
|
if newKey[-1] == "^":
|
|
newKey = newKey[0:-1]
|
|
|
|
return newKey
|
|
|
|
# Calculates the Ceiling grid based on the specified CBP and Sky grids.
|
|
def makeCeiling(self, CloudBasePrimary, Sky):
|
|
|
|
|
|
# Initialize the Ceiling grid to -30000
|
|
ceiling = np.zeros(CloudBasePrimary.shape, np.float32) + -30000.0
|
|
|
|
if CloudBasePrimary is None or Sky is None:
|
|
return ceiling
|
|
|
|
# Find points above the sky threshold
|
|
mask = (Sky > self._ceilingSkyThreshold)
|
|
|
|
# Assign the CPB value at these points
|
|
ceiling[mask] = CloudBasePrimary[mask] * 100 # convert to feet from 100s of feet
|
|
|
|
changeMask = np.ones(ceiling.shape, np.bool) # Always reset all the Ceiling values
|
|
|
|
return ceiling, changeMask
|
|
|
|
# Removes all Visibility components from the Wx ugly string
|
|
def removeVis(self, wxGrid):
|
|
|
|
byteGrid, wxKeys = wxGrid
|
|
|
|
for i in range(len(wxKeys)):
|
|
|
|
subKeys = wxKeys[i].split("^")
|
|
|
|
newWxKey = ""
|
|
for subKey in subKeys:
|
|
wxParts = subKey.split(":")
|
|
|
|
if len(wxParts) >= 4:
|
|
visStr = wxParts[3]
|
|
if visStr not in ["<NoVis>", ""]:
|
|
newSubKey = wxParts[0] + ":" + wxParts[1] + ":" + wxParts[2] + ":" + \
|
|
"<NoVis>" + ":"
|
|
if len(wxParts) == 5 and wxParts[4] != "":
|
|
newSubKey = newSubKey + wxParts[4]
|
|
else:
|
|
newSubKey = subKey
|
|
|
|
newWxKey = newWxKey + newSubKey + "^"
|
|
else:
|
|
newWxKey = newWxKey + subKey + "^"
|
|
|
|
if newWxKey[-1] == "^":
|
|
newWxKey = newWxKey[0:-1]
|
|
|
|
wxKeys[i] = newWxKey
|
|
|
|
return byteGrid, wxKeys
|
|
|
|
|
|
# Returns a new Wx grid that contains the corresponding Visibility string
|
|
# based on the Visibility at that same point for all values less than
|
|
# the specified visThreshold.
|
|
def setWxVis(self, Wx, Visibility, visThreshold):
|
|
changeMask = self.empty(np.bool)
|
|
|
|
# Define the ranges for each visibility category
|
|
# Note that the first value in the range is inclusive and the last
|
|
# value is exclusive, thus the last value matches the next first value.
|
|
visDict = {
|
|
"0SM" : (0.0, 0.125),
|
|
"1/4SM" : (0.125, 0.375),
|
|
"1/2SM" : (0.375, 0.675),
|
|
"3/4SM" : (0.675, 0.875),
|
|
"1SM" : (0.875, 1.25),
|
|
"11/2SM" : (1.25, 1.75),
|
|
"2SM" : (1.75, 2.25),
|
|
"21/2SM" : (2.25, 2.75),
|
|
"3SM" : (2.75, 3.50),
|
|
"4SM" : (3.50, 4.50),
|
|
"5SM" : (4.50, 5.50),
|
|
"6SM" : (5.50, 6.50),
|
|
"P6SM" : (6.50, 99.00),
|
|
}
|
|
|
|
# For each Vis category modify the Wx grid and save it.
|
|
Wx = self.removeVis(Wx)
|
|
|
|
wxGrid, wxKeys = Wx # Decompose the weather grid
|
|
for visStr in visDict:
|
|
visMin, visMax = visDict[visStr]
|
|
# make a mask for the point we plan to modify
|
|
visMask = (Visibility >= visMin) & (Visibility < visMax) & (Visibility <= visThreshold) & self._editAreaMask
|
|
if sum(sum(visMask)) == 0: # don't bother with empty masks
|
|
continue
|
|
|
|
for wxKey in wxKeys:
|
|
wxIndex = self.getIndex(wxKey, wxKeys)
|
|
wxMask = wxGrid == wxIndex
|
|
|
|
overlap = wxMask & visMask
|
|
|
|
if sum(sum(overlap)) == 0: # don't bother with empty masks
|
|
continue
|
|
|
|
newKey = self.addVisToWxKey(wxKey, visStr)
|
|
newWxIndex = self.getIndex(newKey, wxKeys)
|
|
|
|
wxGrid[overlap] = newWxIndex
|
|
changeMask = changeMask | overlap
|
|
|
|
return (wxGrid, wxKeys), changeMask
|
|
|
|
# Check to determine if the grid forecast has no gaps
|
|
def inventoryIsComplete(self, weList, timeRange):
|
|
for weName in weList:
|
|
# Skip Ceiling since the tool will derive that element
|
|
if weName in self._notRequiredWEs:
|
|
continue
|
|
|
|
trList = self.getWEInventory("Fcst", weName, timeRange)
|
|
# Check for any gaps for this WE
|
|
if len(trList) == 0:
|
|
#self.statusBarMsg("Some " + weName + " grids missing. Inventory check failed.", "S")
|
|
return False
|
|
|
|
# Check for the first grid. Start time must be at or before the timeRange
|
|
if trList[0].startTime() > timeRange.startTime():
|
|
#self.statusBarMsg("Some " + weName + " grids missing. Inventory check failed.", "S")
|
|
return False
|
|
|
|
endTime = trList[0].endTime()
|
|
for i in range(1, len(trList)):
|
|
if trList[i].startTime() != endTime:
|
|
#self.statusBarMsg("Some " + weName + " grids missing. Inventory check failed.", "S")
|
|
return False
|
|
endTime = trList[i].endTime()
|
|
|
|
# Check for the last grid
|
|
if trList[-1].endTime().unixTime() < timeRange.endTime().unixTime():
|
|
#self.statusBarMsg("Some " + weName + " grids missing. Inventory check failed.", "S")
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
# Returns true if the tool is configured to offer the Save and Publich option.
|
|
def saveAndPublish(self):
|
|
if self._config["OfferSavePublish"] != "Yes":
|
|
return False
|
|
if self._savePublishVar.get() == "Yes":
|
|
return True
|
|
|
|
return False
|
|
|
|
# Check to ensure that all the data specified are available
|
|
def allDataAvailable(self, weDict, weList, tr):
|
|
for we in weList:
|
|
if we in weDict:
|
|
if tr not in weDict[we]:
|
|
return False
|
|
|
|
return True
|
|
|
|
# Reads the Ceiling and visibility grids and copies them into the CigHgt and Vsby
|
|
# grids, respectively.
|
|
def copyCeilingVisToCigHgtVsby(self):
|
|
|
|
weTuples = [("Ceiling", "CigHgt"), ("Visibility", "Vsby")]
|
|
|
|
|
|
cigMin, cigMax = self.getParmMinMaxLimits("Fcst", "CigHgt")
|
|
|
|
for sourceWE, targetWE in weTuples:
|
|
|
|
minValue, maxValue = self.getParmMinMaxLimits("Fcst", targetWE)
|
|
|
|
trList = self.getWEInventory("Fcst", sourceWE)
|
|
for tr in trList:
|
|
sourceGrid = self.getGrids("Fcst", sourceWE, "SFC", tr)
|
|
|
|
# clip the grid in case the target has different min, max allowed values
|
|
sourceGrid = sourceGrid.clip(minValue, maxValue)
|
|
|
|
self.createGrid("Fcst", targetWE, "SCALAR", sourceGrid, tr)
|
|
|
|
return
|
|
|
|
# Main method that calls all of the selected rules.
|
|
def applyFinalizeRules(self, editArea):
|
|
selectedTR = TimeRange.TimeRange(AbsTime.AbsTime(self._editStartTime),
|
|
AbsTime.AbsTime(self._editEndTime))
|
|
|
|
# Get the GUI variables
|
|
wxToAdd = self._selectedWxType.get()
|
|
|
|
coverageDict = {}
|
|
covList = self._wxTypeCovDict[wxToAdd]
|
|
for cov in covList:
|
|
coverageDict[cov] = self._scales[cov].get()
|
|
|
|
TMinusTd = self._scales[self._TMinusTdScaleName].get()
|
|
self._addVisBelow = self._addVisToWxScale.get()
|
|
|
|
trList = self.getTimeRanges(selectedTR)
|
|
# Make a mask over the edit are and restrict changes to that
|
|
self._editAreaMask = self.encodeEditArea(editArea)
|
|
if np.sum(self._editAreaMask) == 0:
|
|
self._editAreaMask = np.ones(self.getGridShape(), np.bool)
|
|
|
|
# Check the inventory for gaps and Stop or interpolate based on config setting
|
|
inventoryComplete = self.inventoryIsComplete(self._weList, selectedTR)
|
|
if not inventoryComplete:
|
|
if self._config["GapAction"] == "Stop":
|
|
self.statusBarMsg("Fcst gaps found but configured GapAction is set to Stop. Stopping Aviation_Finalize", "S")
|
|
return
|
|
|
|
if self._config["GapAction"] == "Interpolate":
|
|
# Make a list of WEs. Start with the total set and remove those not required.
|
|
weList = []
|
|
for we in self._weList:
|
|
if we not in self._notRequiredWEs:
|
|
weList.append(we)
|
|
self.interpolateCmd(weList, selectedTR, "GAPS", "SYNC", interval=1, duration=1)
|
|
|
|
# Fetch all the grids for the defined elements over the selected timeRange
|
|
weDict = self.fetchAllGrids(self._weList, selectedTR)
|
|
|
|
# Remove Pot grids if configured
|
|
if self._config["MakePotFog"] == "Yes" and "Vis Adds New Wx" in self._userSelectedRules:
|
|
self.deleteCmd(["PotFog", "PotFreezingFog", "PotHaze", "PotSmoke"], selectedTR)
|
|
|
|
# Run the rules
|
|
for tr in trList:
|
|
if "PoP Adjusts Cloud Base" in self._userSelectedRules:
|
|
if self.allDataAvailable(weDict, ["PoP", "CloudBasePrimary"], tr):
|
|
weDict["CloudBasePrimary"][tr] = self.popAssignsCBP(weDict["PoP"][tr][0], weDict["CloudBasePrimary"][tr][0],
|
|
weDict["Sky"][tr][0], weDict["Visibility"][tr][0])
|
|
|
|
if "PoP Defines Sky" in self._userSelectedRules:
|
|
if self.allDataAvailable(weDict, ["PoP", "Sky"], tr):
|
|
weDict["Sky"][tr] = self.popAssignsSky(weDict["PoP"][tr][0], weDict["Sky"][tr][0])
|
|
|
|
if "Vis Adds New Wx" in self._userSelectedRules:
|
|
if self.allDataAvailable(weDict, ["Visibility", "T", "Td", "Wx"], tr):
|
|
grid, mask = self.addNewWxFromVis(weDict["Visibility"][tr][0], weDict["T"][tr][0], weDict["Td"][tr][0],
|
|
weDict["Wx"][tr][0], wxToAdd, TMinusTd, tr)
|
|
finalMask = weDict["Wx"][tr][1] | mask # Accumulate the points
|
|
weDict["Wx"][tr] = grid, finalMask
|
|
|
|
if "Wx Adjusts Vis" in self._userSelectedRules:
|
|
if self.allDataAvailable(weDict, ["Wx", "Visibility"], tr):
|
|
grid, mask = self.updateVisFromWx(weDict["Wx"][tr][0], weDict["Visibility"][tr][0])
|
|
finalMask = weDict["Visibility"][tr][1] | mask # Accumulate the points
|
|
weDict["Visibility"][tr] = grid, finalMask
|
|
|
|
if "QC Vis with PoP & Wx" in self._userSelectedRules:
|
|
if self.allDataAvailable(weDict, ["PoP", "Wx", "Visibility"], tr):
|
|
grid, mask = self.lowPoPNoWxVis10(weDict["PoP"][tr][0], weDict["Wx"][tr][0], weDict["Visibility"][tr][0])
|
|
finalMask = weDict["Visibility"][tr][1] | mask # Accumulate the points
|
|
weDict["Visibility"][tr] = grid, finalMask
|
|
|
|
if "Update Wx Intensity Using Vis" in self._userSelectedRules:
|
|
if self.allDataAvailable(weDict, ["Visibility", "Wx"], tr):
|
|
grid, mask = self.updateWxIntensWithVis(weDict["Visibility"][tr][0], weDict["Wx"][tr][0])
|
|
finalMask = weDict["Wx"][tr][1] | mask # Accumulate the points
|
|
weDict["Wx"][tr] = grid, finalMask
|
|
|
|
if "Adjust WindGust Using Wind" in self._userSelectedRules:
|
|
if self.allDataAvailable(weDict, ["Wind", "WindGust"], tr):
|
|
weDict["WindGust"][tr] = self.updateWindGustWithWind(weDict["Wind"][tr][0], weDict["WindGust"][tr][0])
|
|
|
|
if "Add Vis to Wx String" in self._userSelectedRules:
|
|
if self.allDataAvailable(weDict, ["Wx", "Visibility"], tr):
|
|
grid, mask = self.setWxVis(weDict["Wx"][tr][0], weDict["Visibility"][tr][0], self._addVisBelow)
|
|
finalMask = weDict["Wx"][tr][1] | mask # Accumulate the points
|
|
weDict["Wx"][tr] = grid, finalMask
|
|
|
|
# Ensure there are no zero ceiling values if Sky is not OVC and Vis > 0.5 miles
|
|
# Note this is not an optional rule like the others.
|
|
if self.allDataAvailable(weDict, ["CloudBasePrimary", "Sky", "Visibility"], tr):
|
|
grid, mask = self.noZeroCeiling(weDict["CloudBasePrimary"][tr][0], weDict["Sky"][tr][0],
|
|
weDict["Visibility"][tr][0])
|
|
finalMask = weDict["CloudBasePrimary"][tr][1] | mask # Accumulate the points
|
|
weDict["CloudBasePrimary"][tr] = grid, finalMask
|
|
|
|
if self.allDataAvailable(weDict, ["CloudBasePrimary", "Sky"], tr):
|
|
weDict["Ceiling"][tr] = self.makeCeiling(weDict["CloudBasePrimary"][tr][0],
|
|
weDict["Sky"][tr][0])
|
|
|
|
for weName in self._weList:
|
|
# No need to update every element
|
|
if weName in self._immutableWEs:
|
|
continue
|
|
|
|
weType = self.getWEType(weName)
|
|
if weType is None:
|
|
continue
|
|
# Fetch the grid and mask
|
|
if tr in weDict[weName]:
|
|
grid, mask = weDict[weName][tr]
|
|
if mask.sum() == 0: # don't modify grids that don't need it
|
|
continue
|
|
else: # save the grid
|
|
self.createGrid("Fcst", weName, weType, grid, tr)
|
|
|
|
# Copy the Celing and Visibility grids to the legact elements.
|
|
self.copyCeilingVisToCigHgtVsby()
|
|
|
|
# Save and Publish these elements
|
|
if self.saveAndPublish():
|
|
#print "Saving and Publishing Elements:", self._publishWEList
|
|
self.saveElements(self._publishWEList)
|
|
self.publishElements(self._publishWEList, selectedTR)
|
|
# Save and publish the PoWt grids if configured
|
|
if self._config["MakePotFog"] == "Yes":
|
|
#print "Saving and Publishing PoT Elements:", self._potElements
|
|
self.saveElements(self._potElements)
|
|
self.publishElements(self._potElements, selectedTR)
|
|
|
|
|
|
return
|
|
|
|
# GUI code ################################################################################################
|
|
|
|
# Called when the cancel button is clicked
|
|
def cancelCommand(self):
|
|
self._tkmaster.destroy()
|
|
return
|
|
|
|
# Called when the Run button is clicked
|
|
def runCommand(self):
|
|
self.applyFinalizeRules(self._editArea)
|
|
return
|
|
|
|
# Called when the Run and Dismiss button is clicked
|
|
def runDismissCommand(self):
|
|
self.applyFinalizeRules(self._editArea)
|
|
self.cancelCommand()
|
|
return
|
|
|
|
# Create the Execute and Cancel buttons
|
|
def makeBottomButtons(self, frame, row):
|
|
|
|
# Make the frame
|
|
self._bottomButtonFrame = tk.Frame(frame, bg=self._bgColor)
|
|
self._bottomButtonFrame.grid(row=row, column = 0, columnspan=6, pady=20)
|
|
|
|
# Run button
|
|
self._runButton = tk.Button(self._bottomButtonFrame, text="Run",
|
|
command=self.runCommand)
|
|
self._runButton.grid(row=0, column=0, padx=20)
|
|
|
|
# Run/Dismiss buttom
|
|
self._runDismissButton = tk.Button(self._bottomButtonFrame, text="Run/Dismiss",
|
|
command=self.runDismissCommand)
|
|
self._runDismissButton.grid(row=0, column=1, padx=20)
|
|
# Cancel button
|
|
self._cancelButton = tk.Button(self._bottomButtonFrame, text="Cancel",
|
|
command=self.cancelCommand)
|
|
self._cancelButton.grid(row=0, column=2, padx=20)
|
|
|
|
return
|
|
|
|
# Called when the cursor enter the GUI. Just fetches the current timeRange
|
|
def enterEvent(self, event=None):
|
|
return
|
|
|
|
# Toggles on the recently selected rule button
|
|
def ruleSelected(self, event, ruleName):
|
|
|
|
# toggle on
|
|
if ruleName not in self._userSelectedRules and \
|
|
self._ruleDict[ruleName].cget("state") == tk.ACTIVE:
|
|
self._userSelectedRules.append(ruleName)
|
|
self._ruleDict[ruleName].select()
|
|
if ruleName == "Vis Adds New Wx":
|
|
self.displayAddWxFrame()
|
|
elif ruleName == "Add Vis to Wx String":
|
|
self.displayAddVisToWxFrame()
|
|
elif ruleName == "Update Wx Intensity Using Vis":
|
|
self.displayIntensityFrame()
|
|
elif ruleName in self._userSelectedRules and \
|
|
self._ruleDict[ruleName].cget("state") == tk.ACTIVE:
|
|
if ruleName in self._userSelectedRules:
|
|
self._userSelectedRules.remove(ruleName)
|
|
self._ruleDict[ruleName].deselect()
|
|
if ruleName == "Vis Adds New Wx":
|
|
self.removeAddWxFrame()
|
|
elif ruleName == "Add Vis to Wx String":
|
|
self.removeAddVisToWxFrame()
|
|
elif ruleName == "Update Wx Intensity Using Vis":
|
|
self.removeIntensityFrame()
|
|
|
|
return
|
|
|
|
# Makes the rule buttons on the GUI
|
|
def makeRuleCheckButtons(self, frame):
|
|
|
|
# Make the element frame for selecting the source
|
|
ruleFrame = tk.Frame(frame, relief=tk.GROOVE, bd=3, bg=self._bgColor)
|
|
ruleFrame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
|
|
|
|
# make a label
|
|
label = tk.Label(ruleFrame, text="Rules To Apply:")
|
|
label.grid(row=0, column=0)
|
|
|
|
self._ruleList = self._config["AllChecks"]
|
|
|
|
# Make each source button
|
|
row = 1
|
|
for i in range(len(self._ruleList)):
|
|
ruleName = self._ruleList[i]
|
|
def cbHandler(event, self=self, buttonName=ruleName):
|
|
return self.ruleSelected(event, buttonName)
|
|
self._ruleDict[ruleName] = tk.Checkbutton(ruleFrame, text=ruleName)
|
|
|
|
self._ruleDict[ruleName].grid(row=row, sticky=tk.W, pady=10)
|
|
self._ruleDict[ruleName].bind("<ButtonRelease-1>", cbHandler)
|
|
self._ruleDict[ruleName].bind("<ButtonRelease-2>", cbHandler)
|
|
if ruleName in self._defaultOnRules:
|
|
self._ruleDict[ruleName].select()
|
|
|
|
if ruleName in self._immutableRules:
|
|
self._ruleDict[ruleName].config(state=tk.DISABLED)
|
|
|
|
button_ttp = CreateToolTip(self._ruleDict[ruleName],
|
|
self._config["toolTips"][ruleName])
|
|
|
|
row = row + 1
|
|
|
|
if "Vis Adds New Wx" not in self._defaultOnRules:
|
|
self.removeAddWxFrame()
|
|
if "Add Vis to Wx String" not in self._defaultOnRules:
|
|
self.removeAddVisToWxFrame()
|
|
if "Update Wx Intensity Using Vis" not in self._defaultOnRules:
|
|
self.removeIntensityFrame()
|
|
|
|
# Called when the Wx type button is selected
|
|
def wxTypeSelected(self):
|
|
if self._prevWxType is not None:
|
|
self.removeCovSliders(self._prevWxType)
|
|
|
|
self.makeCovSliders(self._selectedWxType.get())
|
|
|
|
self._prevWxType = self._selectedWxType.get()
|
|
|
|
return
|
|
|
|
# Called when any slider is moved
|
|
def sliderChanged(self, sliderValue):
|
|
|
|
wxType = self._selectedWxType.get()
|
|
covList = self._wxTypeCovDict[self._selectedWxType.get()]
|
|
for cov in covList:
|
|
# Update the label
|
|
label = cov + " " + wxType + " where Vis < " + "%.2f" % self._scales[cov].get()
|
|
if cov == covList[0]: # last item
|
|
label = cov + " " + wxType + " where Vis < " + "%.2f" % self._scales[cov].get()
|
|
|
|
self._scales[cov].config(label=label)
|
|
|
|
# Update the T-Td Slider label
|
|
if wxType == "Fog":
|
|
#scale = self._scales[self._TMinusTdScaleName]
|
|
label = "Fog where T - Td <= " + str(self._scales[self._TMinusTdScaleName].get())
|
|
self._scales[self._TMinusTdScaleName].config(label=label)
|
|
self._scales[self._TMinusTdScaleName].grid_propagate(0)
|
|
|
|
# Adjust the other sliders so they stay ordered
|
|
activeSliderValue = float(sliderValue)
|
|
activeIndex = 0
|
|
for cov in covList:
|
|
if self._scales[cov].get() == activeSliderValue:
|
|
activeIndex = covList.index(cov)
|
|
|
|
for i, cov in enumerate(covList):
|
|
if i == activeIndex:
|
|
continue
|
|
if i > activeIndex and self._scales[cov].get() > activeSliderValue:
|
|
self._scales[cov].set(activeSliderValue + 0.01)
|
|
elif i < activeIndex and self._scales[cov].get() < activeSliderValue:
|
|
self._scales[cov].set(activeSliderValue - 0.01)
|
|
return
|
|
|
|
# Removes Coverage sliders
|
|
def removeCovSliders(self, wxType):
|
|
|
|
self._sliderFrame.destroy()
|
|
|
|
return
|
|
# Makes the Coverage sliders for the GUI
|
|
def makeCovSliders(self, wxType):
|
|
|
|
covList = self._wxTypeCovDict[wxType]
|
|
self._scaleVar = {}
|
|
row = self._startCovRow
|
|
|
|
self._sliderFrame = tk.Frame(self._addWxFrame, relief=tk.GROOVE, bd=3, bg=self._bgColor)
|
|
self._sliderFrame.grid(row=self._startCovRow, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
|
|
|
|
# Add the Dew point depression scale
|
|
if wxType == "Fog":
|
|
self._TMinusTdScaleName = "TMinusTd"
|
|
self._scaleVar[self._TMinusTdScaleName] = tk.DoubleVar()
|
|
self._scaleVar[self._TMinusTdScaleName].set(self._config["T-TdDefault"])
|
|
self._scales[self._TMinusTdScaleName] = tk.Scale(self._sliderFrame, from_= 0, to=10,
|
|
orient=tk.HORIZONTAL,
|
|
command=self.sliderChanged,
|
|
resolution=1, length=200,
|
|
sliderlength=20,
|
|
variable=self._scaleVar[self._TMinusTdScaleName])
|
|
self._scales[self._TMinusTdScaleName].grid(row=row, column=1, sticky=tk.E+tk.W)
|
|
self._scales[self._TMinusTdScaleName].grid_propagate(0)
|
|
self._scales[self._TMinusTdScaleName].name = self._TMinusTdScaleName
|
|
|
|
row = row + 1
|
|
|
|
for cov in covList:
|
|
self._scaleVar[cov] = tk.DoubleVar()
|
|
covKey = wxType + "_" + cov
|
|
defaultValue = self._config[covKey]
|
|
self._scaleVar[cov].set(defaultValue)
|
|
|
|
self._scales[cov] = tk.Scale(self._sliderFrame, from_= 0, to=6,
|
|
orient=tk.HORIZONTAL,
|
|
command=self.sliderChanged,
|
|
resolution=0.01, length=200,
|
|
sliderlength=20,
|
|
variable=self._scaleVar[cov])
|
|
self._scales[cov].grid(row=row, column=1, sticky=tk.E+tk.W)
|
|
self._scales[cov].grid_propagate(0)
|
|
self._scales[cov].name = cov
|
|
row = row + 1
|
|
|
|
return
|
|
|
|
# Method that draws the widgets for adding new Wx
|
|
def makeAddNewWxWidgets(self, frame):
|
|
|
|
self._addWxFrame = tk.Frame(frame, relief=tk.GROOVE, bd=3, bg=self._bgColor)
|
|
self._addWxFrame.grid(row=0, column=1, sticky=tk.N+tk.E+tk.S+tk.W, columnspan=2)
|
|
|
|
# Make the Wx type buttons
|
|
label = tk.Label(self._addWxFrame, text="Add Wx Type", font=self._activeFont)
|
|
label.grid(row=1, column=0)
|
|
# Make each source button
|
|
row = 2
|
|
|
|
wxTypeList = sorted(self._wxTypeCovDict.keys())
|
|
self._selectedWxType = tk.StringVar()
|
|
self._selectedWxType.set(self._defaultWxType)
|
|
self._prevWxType = None
|
|
for i in range(len(wxTypeList)):
|
|
name = wxTypeList[i]
|
|
self._wxTypeDict[name] = tk.Radiobutton(self._addWxFrame, text=wxTypeList[i], padx=10, pady=2,
|
|
command=self.wxTypeSelected, variable=self._selectedWxType,
|
|
value = wxTypeList[i])
|
|
self._wxTypeDict[name].grid(row=row, sticky=tk.W)
|
|
|
|
row = row + 1
|
|
|
|
self._startCovRow = row
|
|
self.makeCovSliders(self._defaultWxType)
|
|
|
|
return
|
|
|
|
# These methods draw or remove their respective frames
|
|
def removeAddWxFrame(self):
|
|
self._addWxFrame.grid_remove()
|
|
return
|
|
|
|
def displayAddWxFrame(self):
|
|
self._addWxFrame.grid(row=0, column=1, sticky=tk.N+tk.E+tk.S+tk.W)
|
|
return
|
|
|
|
def removeAddVisToWxFrame(self):
|
|
self._addVisToWxFrame.grid_remove()
|
|
return
|
|
|
|
def displayAddVisToWxFrame(self):
|
|
self._addVisToWxFrame.grid(row=self._addVisToWxRow, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
|
|
return
|
|
|
|
def removeIntensityFrame(self):
|
|
self._intFrame.grid_remove()
|
|
return
|
|
|
|
def displayIntensityFrame(self):
|
|
self._intFrame.grid(row=self._addVisToWxRow, column=1, sticky=tk.N+tk.E+tk.S+tk.W)
|
|
return
|
|
|
|
##########################################################################################
|
|
# Time Scale methods
|
|
|
|
# Define the coordinates for the time scale objects
|
|
def defineGeometry(self):
|
|
|
|
|
|
self._xSize = 500 # width of each canvas
|
|
#self._ySize = 150 # height of each canvas
|
|
self._xOffset = 10 # horizontal room for y-scale on left
|
|
|
|
# Time Scale geometry
|
|
self._markerTopY = 10
|
|
self._markerBottomY = 25
|
|
self._markerWidth = 14
|
|
self._markerHeight = 14
|
|
self._adjStartMarker = False
|
|
self._adjEndMarker = False
|
|
self._timeCanvasHeight = 40
|
|
self._tickHeight = 7
|
|
self._timeLabelOffset = 7
|
|
self._labelFreq = 6 # hours
|
|
self._deltaTime = self._endTime - self._startTime
|
|
self._pixPerHour = (self._xSize - self._xOffset - 20) // (self._deltaTime // 3600)
|
|
|
|
return
|
|
|
|
# Converts a time in seconds to the corresponding x-coordinate
|
|
def timeToX(self, timeInSec):
|
|
|
|
timeOffset = float(timeInSec - self._startTime) / 3600
|
|
|
|
#xCoord = self._xOffset + (timeOffset * self._pixPerHour) + 1
|
|
xCoord = self._xOffset + (timeOffset * self._pixPerHour) + 3
|
|
|
|
return xCoord
|
|
|
|
# Converts the x-coordinate to time in seconds
|
|
def xToTime(self, xCoord):
|
|
|
|
coordTime = self._startTime + (((xCoord - self._xOffset - 3) * 3600) // self._pixPerHour)
|
|
|
|
return coordTime
|
|
|
|
# Returns the coordinate of the marker associated with the specifiedTime
|
|
def markerCoords(self, markerTime):
|
|
|
|
startX = self.timeToX(markerTime)
|
|
|
|
x1 = startX - self._markerWidth // 2
|
|
y1 = self._markerBottomY + self._markerHeight
|
|
x2 = startX + self._markerWidth // 2
|
|
y2 = self._markerBottomY + self._markerHeight
|
|
x3 = startX
|
|
y3 = self._markerBottomY
|
|
|
|
return x1, y1, x2, y2, x3, y3
|
|
|
|
# Draws the time marker at the specified time
|
|
def drawTimeMarker(self, markerTime):
|
|
|
|
startX = self.timeToX(markerTime)
|
|
|
|
if markerTime == self._editStartTime:
|
|
color = "green"
|
|
elif markerTime == self._editEndTime:
|
|
color = "red"
|
|
|
|
self._timeCanvas.create_line(startX, self._markerTopY,
|
|
startX, self._markerBottomY, fill=color)
|
|
|
|
x1, y1, x2, y2, x3, y3 = self.markerCoords(markerTime)
|
|
|
|
self._timeCanvas.create_polygon(x1, y1, x2, y2, x3, y3, fill=color,
|
|
outline="black")
|
|
|
|
return
|
|
|
|
# Called when any time marker is pressed
|
|
def markerPress(self, event):
|
|
# Figure out if the start marker was pressed
|
|
self._pressTime = self.xToTime(event.x)
|
|
|
|
closeEnough = 600
|
|
if abs(self._editStartTime - self._pressTime) < closeEnough:
|
|
self._adjStartMarker = True
|
|
elif abs(self._editEndTime - self._pressTime) < closeEnough:
|
|
self._adjEndMarker = True
|
|
return
|
|
|
|
# Called when any time marker is moved or draged
|
|
def markerMotion(self, event):
|
|
markerTime = self.xToTime(event.x)
|
|
# Round time to nearest hour
|
|
markerTime = int((markerTime + 1800) / 3600) * 3600
|
|
|
|
if self._adjStartMarker:
|
|
if markerTime != self._editStartTime:
|
|
if markerTime < self._startTime:
|
|
markerTime = self._startTime
|
|
# Can't go past end time
|
|
if markerTime > self._editEndTime - 3600:
|
|
markerTime = self._editEndTime - 3600
|
|
else:
|
|
self._editStartTime = markerTime
|
|
|
|
self.makeTimeScale()
|
|
return
|
|
elif self._adjEndMarker:
|
|
if markerTime != self._editEndTime:
|
|
if markerTime > self._endTime:
|
|
markerTime = self._endTime
|
|
# Can't go past start time
|
|
if markerTime < self._editStartTime + 3600:
|
|
markerTime = self._editStartTime + 3600
|
|
else:
|
|
self._editEndTime = markerTime
|
|
|
|
self.makeTimeScale()
|
|
return
|
|
|
|
return
|
|
|
|
# Called when any time marker is released
|
|
def markerRelease(self, event):
|
|
|
|
# If it's a click snap the closest time marker
|
|
releaseTime = self.xToTime(event.x)
|
|
|
|
# If it's a click, snap the closest time marker
|
|
if releaseTime == self._pressTime:
|
|
startDiff = abs(self._editStartTime - releaseTime)
|
|
endDiff = abs(self._editEndTime - releaseTime)
|
|
if releaseTime < self._editStartTime or startDiff < endDiff:
|
|
self._adjStartMarker = True
|
|
self.markerMotion(event)
|
|
elif releaseTime > self._editEndTime or startDiff >= endDiff:
|
|
self._adjEndMarker = True
|
|
self.markerMotion(event)
|
|
|
|
self._adjStartMarker = False
|
|
self._adjEndMarker = False
|
|
|
|
return
|
|
|
|
# Creates the timeScale GUI
|
|
def makeTimeScale(self):
|
|
|
|
wList = self._timeFrame.winfo_children()
|
|
for w in wList:
|
|
w.destroy()
|
|
|
|
hours = int((self._editEndTime - self._editStartTime) / 3600)
|
|
if hours > 1:
|
|
labelText = "Select Time Period ----- " + str(hours) + " hours selected"
|
|
else:
|
|
labelText = "Select Time Period ----- " + str(hours) + " hour selected"
|
|
|
|
label = tk.Label(self._timeFrame, text=labelText)
|
|
label.grid(row=0, column=0)
|
|
|
|
|
|
self._timeCanvas = tk.Canvas(self._timeFrame,width=self._xSize,
|
|
height=self._timeCanvasHeight,
|
|
bg='gray')
|
|
self._timeCanvas.grid(row=1)
|
|
|
|
self._timeCanvas.bind("<ButtonPress-" + self._mouseButton + ">", self.markerPress)
|
|
self._timeCanvas.bind("<ButtonRelease-" + self._mouseButton + ">", self.markerRelease)
|
|
#self._timeCanvas.bind("<Leave>", self.markerRelease)
|
|
self._timeCanvas.bind("<Motion>", self.markerMotion)
|
|
|
|
# Make a horizontal line
|
|
x0 = self.timeToX(self._startTime)
|
|
x1 = self.timeToX(self._endTime)
|
|
self._timeCanvas.create_line(x0, 1, x1, 1, fill='black')
|
|
|
|
self.drawTimeMarker(self._editStartTime)
|
|
self.drawTimeMarker(self._editEndTime)
|
|
for t in range(self._startTime, self._endTime + 1, 3600):
|
|
tupleTime = time.gmtime(t)
|
|
dayStr = str(tupleTime.tm_mday).zfill(2)
|
|
hourStr = str(tupleTime.tm_hour).zfill(2)
|
|
timeStr = dayStr + "." + hourStr
|
|
x0 = self.timeToX(t)
|
|
y0 = 0
|
|
tickHeight = self._tickHeight
|
|
if tupleTime.tm_hour % self._labelFreq == 0:
|
|
tickHeight = tickHeight * 2
|
|
|
|
y1 = y0 + tickHeight
|
|
self._timeCanvas.create_line(x0, y0, x0, y1, fill='black')
|
|
|
|
|
|
if tupleTime.tm_hour % self._labelFreq == 0:
|
|
self._timeCanvas.create_text(x0, y1 + self._timeLabelOffset,
|
|
text=timeStr, fill='black')
|
|
return
|
|
|
|
# Adds wx slider
|
|
def addVisToWxSliderChanged(self, event):
|
|
|
|
valueStr = "%.2f" % self._addVisToWx.get()
|
|
|
|
label = "Add Vis to Wx where Vis <= " + valueStr
|
|
self._addVisToWxScale.config(label=label)
|
|
self._addVisToWxScale.grid_propagate(0)
|
|
|
|
return
|
|
|
|
# Draws Vis scales
|
|
def makeAddVisToWxScale(self, frame, row):
|
|
|
|
# make a label
|
|
self._addVisToWxRow = row
|
|
self._addVisToWxFrame = tk.Frame(frame, relief=tk.GROOVE, bd=3)
|
|
self._addVisToWxFrame.grid(row=self._addVisToWxRow, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
|
|
|
|
# label = tk.Label(addVisToWxFrame, text="")
|
|
# label.grid(row=0, column=0)
|
|
|
|
self._addVisToWx = tk.DoubleVar()
|
|
self._addVisToWx.set(self._config["AddVisToWxValue"])
|
|
self._addVisToWxScale = tk.Scale(self._addVisToWxFrame, from_= 0, to=10,
|
|
orient=tk.HORIZONTAL,
|
|
command=self.addVisToWxSliderChanged,
|
|
resolution=0.01, length=220,
|
|
sliderlength=20,
|
|
variable=self._addVisToWx)
|
|
self._addVisToWxScale.grid(row=1, column=0)
|
|
|
|
return
|
|
|
|
# No-opt callback
|
|
def intensitySelected(self):
|
|
return
|
|
|
|
# Creates the Wx Intensity buttons
|
|
def makeIntensityButtons(self, frame, row):
|
|
self._intFrame = tk.Frame(frame, relief=tk.GROOVE, bd=3)
|
|
self._intFrame.grid(row=row, column=1, sticky=tk.N+tk.S+tk.E+tk.W)
|
|
|
|
label = tk.Label(self._intFrame, text="Vis Updates\nWx Intensity of:")
|
|
label.grid(row=0, column=0)
|
|
# Make Intensity Buttons
|
|
intensityDefault = self._config["IntensityDefault"]
|
|
self._intVar = tk.StringVar()
|
|
self._intVar.set(intensityDefault)
|
|
choiceList = ["Precip", "Non-Precip"]
|
|
row = 1
|
|
for choice in choiceList:
|
|
button = tk.Radiobutton(self._intFrame, text=choice, padx=10, pady=2,
|
|
command=self.intensitySelected, variable=self._intVar,
|
|
value = choice)
|
|
button.grid(row=row)
|
|
row = row + 1
|
|
return
|
|
|
|
# Call back when Save/Pub selected. Nothing to do.
|
|
def savePubSelected(self):
|
|
return
|
|
|
|
# Makes the Save and Publish buttons
|
|
def makeSavePublishButtons(self, frame, row):
|
|
if self._config["OfferSavePublish"] != "Yes":
|
|
return
|
|
|
|
savePubFrame = tk.Frame(frame, relief=tk.GROOVE, bd=3)
|
|
savePubFrame.grid(row=row, column=2, sticky=tk.N+tk.S+tk.E+tk.W, columnspan=3)
|
|
|
|
label = tk.Label(savePubFrame, text="Save and\nPublish?")
|
|
label.grid(row=0, column=0)
|
|
# Make Save/Publish Buttons
|
|
savePubDefault = self._config["SavePublishDefault"]
|
|
self._savePublishVar = tk.StringVar()
|
|
self._savePublishVar.set(savePubDefault)
|
|
choiceList = ["Yes", "No"]
|
|
row = 1
|
|
for choice in choiceList:
|
|
button = tk.Radiobutton(savePubFrame, text=choice, padx=10, pady=2,
|
|
command=self.savePubSelected, variable=self._savePublishVar,
|
|
value = choice)
|
|
button.grid(row=row)
|
|
row = row + 1
|
|
return
|
|
|
|
# This code displays makes the window display at the cursor location
|
|
def displayWindowOnCursor(self):
|
|
self._master.update_idletasks()
|
|
wh= self._master.winfo_height()
|
|
ww= self._master.winfo_width()
|
|
px, py =self._master.winfo_pointerxy()
|
|
self._master.geometry("%dx%d+%d+%d" % (ww, wh, px - (ww // 2), py - (wh // 2)))
|
|
return
|
|
|
|
# High-level method that calls other methods to build the GUI a widget at a time
|
|
def setUpUI(self):
|
|
|
|
self._tkmaster = tk.Tk()
|
|
self._master = tk.Toplevel(self._tkmaster)
|
|
|
|
self._master.title("Aviation_Finalize")
|
|
|
|
# Capture the "x" click to close the GUI
|
|
self._master.protocol('WM_DELETE_WINDOW', self.cancelCommand)
|
|
|
|
# self._topFrame = tk.Frame(self._master, bg=self._bgColor)
|
|
self._topFrame = tk.Frame(self._master, width=450)
|
|
self._topFrame.grid()
|
|
self._tkmaster.withdraw() # remove the master from the display
|
|
self._topFrame.bind("<Enter>", self.enterEvent)
|
|
|
|
# Make the GUI widgets
|
|
row = 1
|
|
self.makeAddVisToWxScale(self._topFrame, row)
|
|
self.makeSavePublishButtons(self._topFrame, row)
|
|
self.makeIntensityButtons(self._topFrame, row)
|
|
row = row + 1
|
|
self.makeAddNewWxWidgets(self._topFrame)
|
|
self.makeRuleCheckButtons(self._topFrame)
|
|
|
|
# row = 1
|
|
# self.makeAddVisToWxScale(self._topFrame, row)
|
|
# self.makeSavePublishButtons(self._topFrame, row)
|
|
# row = row + 1
|
|
|
|
self._timeScaleRow = row
|
|
self._timeFrame = tk.Frame(self._topFrame, pady=5, relief=tk.GROOVE, bd=3)
|
|
self._timeFrame.grid(row=self._timeScaleRow, columnspan=3, sticky=tk.N)
|
|
self.makeTimeScale()
|
|
|
|
|
|
row = row + 1
|
|
self.makeBottomButtons(self._topFrame, row)
|
|
|
|
self.displayWindowOnCursor()
|
|
|
|
return
|
|
|
|
# Algorithm to determine the start and end edit times, courtesy of Marcia Cronce.
|
|
def determineTAFTimes(self):
|
|
|
|
# Find the time rounded up to the next three hours
|
|
start = (int(time.time() / (3600 * 3)) * (3600 * 3)) + (3600 * 3)
|
|
hour = time.gmtime(start).tm_hour
|
|
|
|
if hour % 6 == 0:
|
|
end = start + (31 * 3600)
|
|
else:
|
|
end = start + (37 * 3600)
|
|
|
|
return start, end
|
|
|
|
def filterWeatherElements(self, weList):
|
|
goodWEs = []
|
|
|
|
for weName in weList:
|
|
parm = self.getParm("Fcst", weName, "SFC")
|
|
if parm is not None:
|
|
goodWEs.append(weName)
|
|
|
|
return goodWEs
|
|
|
|
# Main method called when the tool starts.
|
|
def execute(self, editArea, timeRange):
|
|
# Get the config dictionary for this tool
|
|
self._config = FinalizeConfig.config["Finalize"]
|
|
|
|
self._timeRange = timeRange
|
|
self._editArea = editArea
|
|
|
|
# set up some constants for this tool
|
|
self._activeFont = "Helvetica 12 bold"
|
|
self._disabledFont = "Helvetica 12 normal"
|
|
self._bgColor = "#d9d9d9"
|
|
|
|
self._defaultOnRules = copy.copy(self._config["DefaultOnChecks"])
|
|
self._userSelectedRules = copy.copy(self._config["DefaultOnChecks"])
|
|
self._immutableRules = copy.copy(self._config["ImmutableChecks"])
|
|
|
|
self._noWxStr = "<NoCov>:<NoWx>:<NoInten>:<NoVis>:"
|
|
|
|
self._ruleDict = {}
|
|
|
|
self._wxTypeDict = {}
|
|
self._scales = {}
|
|
self._defaultWxType = "Fog"
|
|
|
|
self._ceilingSkyThreshold = 57.0 # percentage above which we can get a Ceiling
|
|
|
|
# Comprehensive list of elements we need.
|
|
self._weList = ["PoP", "CloudBasePrimary", "CloudBaseSecondary", "CloudBaseConditional",
|
|
"Wx", "Sky", "T", "Td", "Visibility", "VisibilityConditional", "Wind",
|
|
"WindGust", "Ceiling", "LLWS", "LLWSHgt", "SkySecondary"]
|
|
# List of elements we will publish no matter what
|
|
self._publishWEList = ["PoP", "CloudBasePrimary", "CloudBaseSecondary", "CloudBaseConditional",
|
|
"Wx", "Sky", "Visibility", "VisibilityConditional", "SkySecondary",
|
|
"Wind", "WindGust", "Ceiling", "LLWS", "LLWSHgt", "CigHgt", "Vsby"]
|
|
|
|
# Filter out elements that are not configured, otherwise saving and publishing
|
|
# throw errors.
|
|
self._publishWEList = self.filterWeatherElements(self._publishWEList)
|
|
|
|
# # Define the Pot elements if we're making those
|
|
# if self._config["MakePotFog"] == "Y":
|
|
self._potElements = ["PotFog", "PotFreezingFog", "PotHaze", "PotSmoke"]
|
|
self._potElements = self.filterWeatherElements(self._potElements)
|
|
|
|
# These elements are not required
|
|
self._notRequiredWEs = ["Ceiling", "CloudBaseSecondary", "CloudBaseConditional", "VisibilityConditional",
|
|
"SkySecondary"]
|
|
# These are elements used to keep others consistent and are not modified
|
|
self._immutableWEs = ["PoP", "T", "Td", "Wind"]
|
|
|
|
self._wxTypeCovDict = {
|
|
"Fog" : ["Patchy", "Areas", "Wide"],
|
|
"Haze" : ["Def"],
|
|
"Smoke" : ["Patchy", "Areas", "Def"],
|
|
}
|
|
|
|
# List of all precipitation types
|
|
self._obstructionTypes = ["F", "H", "K", "ZF"]
|
|
|
|
# Initialize time variables
|
|
startHour = self._config["TAFStartHour"]
|
|
endHour = self._config["TAFEndHour"]
|
|
self._startTime = int(self._gmtime().unixTime() / 3600) * 3600 + (startHour * 3600)
|
|
self._endTime = self._startTime + (endHour * 3600)
|
|
|
|
# Get the initial GFE selected start and end times
|
|
gfeStart = int(timeRange.startTime().unixTime() / 3600) * 3600
|
|
gfeEnd = int(timeRange.endTime().unixTime() / 3600) * 3600
|
|
|
|
# Set the edit start and end times. These may be changed by the user.
|
|
self._editStartTime = self._startTime
|
|
self._editEndTime = self._endTime
|
|
|
|
if self._config["AlwaysUseFullTAFPeriod"] != "Yes":
|
|
# If no time was selected, employ the Marcia method for determining the start and end.
|
|
if gfeStart == 0 and gfeEnd == 0:
|
|
self._editStartTime, self._editEndTime = self.determineTAFTimes()
|
|
if self._editEndTime > self._endTime: # reset the display end time so everything fits
|
|
self._endTime = self._editEndTime
|
|
# Reset the start and end times to what is selected in the GFE, if reasonable
|
|
if gfeStart >= self._editStartTime and gfeStart < self._editEndTime:
|
|
self._editStartTime = gfeStart
|
|
if gfeEnd > self._editStartTime and gfeEnd <= self._editEndTime:
|
|
self._editEndTime = gfeEnd
|
|
|
|
self._mouseButton = self._config["editButton"]
|
|
# Define the geometry variables for the time scale
|
|
self.defineGeometry()
|
|
|
|
self.setUpUI()
|
|
|
|
tk.mainloop()
|
|
|