awips2/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/SelectBreakpoints.py
2022-05-05 12:34:50 -05:00

918 lines
33 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.
#
# SelectBreakpoints
#
# March 10, 2020 21020 tlefebvr Original version
# March 13, 2020 21020 tlefebvr Added dialog to save edits if cancel
# March 13, 2020 21020 tlefebvr Added conflict checking with other storms
# March 13, 2020 21020 tlefebvr Added Storm Number to GUI
# March 20, 2020 21020 tlefebvr Many changes, primarily changed display to
# paint breakpoints instead of coastal zones.
# March 23, 2020 21020 tlefebvr Fixed code to paint all breakpoint segments.
# March 23, 2020 21020 tlefebvr Refactored common code and put in WindWWUtils.
# March 24, 2020 21020 tlefebvr Tool GUI and dialog now pop up near the cursor.
# Added a bit of extra code to handle the special
# case near the TX/MEXICO border.
# March 26, 2020 21020 tlefebvr Added new lat/lon format for XML file. Fixed a
# breakpoint segment sorting bug.
# March 27, 2020 21020 tlefebvr Fixed a couple of bugs related to lat/lon output
# March 30, 2020 21020 tlefebvr Added general way to connect circular BP sequences
# and change the colors of the hazard buttons to match
# the convention.
# April 7, 2020 21020 tlefebvr Fixed selection of breakpoints algorithm
# April 8, 2020 21020 tlefebvr Fixed so zones appear in one and only one hazard.
# April 9, 2020 21020 tlefebvr Fixed hazard order in makeZoneDict.
# April 20, 2020 22033 tlefebvr Added EastPac storm bins.
# May 5, 2020 22033 tlefebvr Added CentralPac storm bins.
# May 6, 2020 22033 tlefebvr Code clean up.
# May 13, 2020 22033 tlefebvr Chance missing BP rank to zero instead of None.
# May 13, 2020 22033 tlefebvr Fixed issue with BP outside domain.
# May 14, 2020 22033 tlefebvr Modified to use ***Sites methods in WWUTils
# May 16, 2020 22033 tlefebvr Changed location of breakpoint data files.
# May 18, 2020 22033 tlefebvr Fixed bugs related to empty breakpoints list.
# Tool now plots all advisories upon exit.
# May 20, 2020 22033 tlefebvr Addressed code review comments.
# May 29, 2020 22033 tlefebvr Addressed code review comment.
# June 3, 2020 22033 tlefebvr Addressed several code review comments.
#
# Author: lefebvre
################################################################################
MenuItems = ["Populate"]
import numpy as np
import AbsTime, TimeRange
import TropicalUtility
import WindWWUtils
import copy
import sys
if sys.version_info.major == 2:
import Tkinter as tk
else:
import tkinter as tk
class Procedure (TropicalUtility.TropicalUtility):
def __init__(self, dbss):
TropicalUtility.TropicalUtility.__init__(self, dbss)
self._dbss = dbss
# Instantiate the WindWWUtils modules
self._WindWWUtils = WindWWUtils.WindWWUtils(self._dbss)
def makeZoneDict(self, bpDict):
"""
Makes a dictionary of {haz : zoneList} based on the bpDict.
This structure is stored in the JSON file.
"""
hazZoneDict = {}
allZoneList = []
reverseHazList = copy.copy(self._hazardOrder)
reverseHazList.reverse()
for haz in reverseHazList:
if haz == "<None>":
continue
if haz not in bpDict:
hazZoneDict[haz] = []
continue
for bp in bpDict[haz]:
zoneList = self._WindWWUtils.getBPZones(bpDict, haz)
if haz not in hazZoneDict:
hazZoneDict[haz] = []
for zone in zoneList:
if zone not in hazZoneDict[haz] and zone not in allZoneList:
hazZoneDict[haz].append(zone)
allZoneList.append(zone)
return hazZoneDict
def getBPLatLon(self, bpName):
"""
Fetches the lan/lon based on the breakpoint name
"""
for bpType, bpDict in self._bpLatLonDict.items():
for item in bpDict.items():
gridCell, (name, lat, lon) = item
if name == bpName:
return lat, lon
self.statusBarMsg(bpName + "not found in Breakpoint Lat/Lon dict.", "S")
return None, None
def dualBP(self, bpName):
"""
Returns true in the bpName is a segment (two names)
"""
return " - " in bpName
def firstBP(self, bpName):
"""
Returns the first BP in the specified dual.
"""
return bpName.split(" - ")[0]
def lastBP(self, bpName):
"""
Returns the last BP in the specified dual.
"""
return bpName.split(" - ")[-1]
def groupBPSegments(self, segmentList):
"""
Returns the specified segmentList as a list of segments lists
grouped by adjacent breakpoint sets.
"""
if not segmentList:
return []
segList = []
groupList = []
for i, segment in enumerate(segmentList):
if not self.dualBP(segment): # Singular BP
if segList:
groupList.append(segList)
segList = []
groupList.append([segment])
else: # DualBP
if i == 0: # Special case for first time
segList.append(segment)
continue
if self.lastBP(segmentList[i-1]) == self.firstBP(segmentList[i]):
segList.append(segment)
else:
groupList.append(segList)
segList = [segment]
if self.dualBP(segmentList[-1]): # If the last segment is a dual
groupList.append(segList) # append the last segList
return groupList
def latLonsFromBreakpoints(self, bpDict, haz):
"""
This method returns a list of lat/lon pairs based on the specified
bpDict and hazard.
"""
latLonList = []
if haz not in bpDict:
return latLonList
bpList = bpDict[haz]
singleBPList = []
for bp in bpList:
parts = bp.split(" - ")
for p in parts:
if p not in singleBPList:
singleBPList.append(p)
sortedSegList = self.makeBPSegments(singleBPList)
# Group the segments in lists of contiguous sets
sortedSegments = self.groupBPSegments(sortedSegList)
hazLatLons = []
for segmentList in sortedSegments:
segLatLons = []
for segment in segmentList:
bpList = segment.split(" - ")
for bp in bpList:
lat, lon = self.getBPLatLon(bp)
if [lat, lon] not in segLatLons:
segLatLons.append([lat, lon])
hazLatLons.append(segLatLons)
return hazLatLons
def makeLatLonDict(self, bpDict):
"""
Makes a dictionary of lat/lon to store in the JSON file.
"""
latLonDict = {}
for haz in self._hazardOrder:
if haz == "<None>":
continue
# Initialize for this hazard
latLonDict[haz] = self.latLonsFromBreakpoints(bpDict, haz)
return latLonDict
def saveStormInfo(self, pil):
"""
Saves the specified stormInfo to the JSON files under pil.
"""
# Find the dict with the matching pil
for (stormPil, stormInfo) in self._stormInfoDict.items():
if stormPil == pil:
# insert the bpDict
stormInfo["Breakpoints"] = self._stormInfoDict[pil]["Breakpoints"]
zoneDict = self.makeZoneDict(stormInfo["Breakpoints"])
stormInfo["zoneDict"] = zoneDict
latLonDict = self.makeLatLonDict(stormInfo["Breakpoints"])
stormInfo["latLonDict"] = latLonDict
# Use TropicalUtility to save advisories.
self._saveAdvisory(pil, stormInfo)
break
return
def saveAllStormInfo(self):
"""
Save all of the stormInfo dicts in the JSON files.
"""
for adv in self._stormInfoDict:
self.saveStormInfo(adv)
self._savingNeeded = False
return
def saveHazards(self):
"""
Save all edited stormInfo
"""
self.saveAllStormInfo()
self.cancelCommand()
return
def saveNotWanted(self):
"""
Called when the user discards edits.
"""
self._savingNeeded = False
self.cancelCommand()
return
# Make a temporary dialog to see if the user wants to continue.
def dialogPrompt(self):
"""
Pops a dialog and asks the user if they want to save before exiting.
"""
self._dialogMaster = tk.Toplevel(self._tkmaster)
self._dialogMaster.title("Save Edits?")
self._dialogMaster.attributes("-topmost", True)
dialogFrame = tk.Frame(self._dialogMaster)
dialogFrame.grid()
label = tk.Label(dialogFrame, text="Your edits have not been saved.")
label.grid(row=0, column=0, columnspan=2)
saveButton = tk.Button(dialogFrame, text="Save", command=self.saveHazards, bg="green")
saveButton.grid(row=1, column=0, padx=20, pady=30)
saveButton = tk.Button(dialogFrame, text="Discard Edits", command=self.saveNotWanted, bg="red")
saveButton.grid(row=1, column=1, padx=20, pady=30)
self._savingNeeded = False
self.displayWindowOnCursor(self._dialogMaster)
tk.mainloop()
return
def cancelCommand(self):
"""
Called when the cancel button is clicked
"""
self.updateDisplay(plotAllStorms=True) # update with all storms
if self._savingNeeded:
self.dialogPrompt()
try:
self._tkmaster.destroy()
except:
pass
def runCommand(self):
"""
Called when run is selected. Just saves the stormInfo for the current pil.
"""
self.saveAllStormInfo()
def runDismissCommand(self):
"""
Called when Run/Dismiss button is clicked.
"""
self.saveAllStormInfo()
self.cancelCommand()
def makeBottomButtons(self, frame):
"""
Create the Execute and Cancel buttons.
"""
# Make the frame
self._bottomButtonFrame = tk.Frame(frame, bg=self._bgColor)
self._bottomButtonFrame.grid(row=5, column=0, pady=20, columnspan=2)
# Cancel button
saveColor = "green"
self._saveButton = tk.Button(self._bottomButtonFrame, text="Save",
command=self.runCommand, bg=saveColor)
self._saveButton.grid(row=0, column=0, padx=20)
# Cancel button
runDismissColor = "lightgreen"
self._saveDismissButton = tk.Button(self._bottomButtonFrame, text="Save/Dismiss",
command=self.runDismissCommand, bg=runDismissColor)
self._saveDismissButton.grid(row=0, column=1, padx=20)
# Cancel button
cancelColor = "red"
self._cancelButton = tk.Button(self._bottomButtonFrame, text="Cancel",
command=self.cancelCommand, bg=cancelColor)
self._cancelButton.grid(row=0, column=2, padx=20)
return
def bpRank(self, breakpoint):
"""
A ranking algorithm used to sort the breakpoints.
"""
for bpType in self._bpLatLonDict:
majorRank = self._bpTypes.index(bpType) + 1
bpDict = self._bpLatLonDict[bpType]
for i, item in enumerate(bpDict.items()):
gridCell, (name, lat, lon) = item
if name == breakpoint:
countryNum = int(self._countryDict[name])
return (majorRank * 1000000) + (countryNum) * 1000 + i
# This should never happen
print(breakpoint, "not found in bpRank.")
return 0
def calcBlobCoords(self, x, y):
"""
Calculates the coordinates of the square used to indicate the BP location.
"""
gridShape = self.getGridShape()
if self._blobSize <= 2:
return y, y+1, x, x+1
else:
inc = int(self._blobSize - 1) // 2
y0 = y - inc
y1 = y + inc + 1
x0 = x - inc
x1 = x + inc + 1
if y0 < 0:
y0 = 0
if y1 > gridShape[0] - 1:
y1 = gridShape[0] - 1
if x0 < 0:
x0 = 0
if x1 > gridShape[1] - 1:
x1 = gridShape[1] - 1
return y0, y1, x0, x1
def getPathMask(self, x1, y1, x2=None, y2=None):
"""
Calculates the mask made by a linear path from x1, y1 to x2, y2.
Missing x2, y2 returns the blob for the first grid point.
"""
mask = self.empty(np.bool)
if not (x2 and y2):
top, bottom,left, right = self.calcBlobCoords(y1, x1)
mask[top:bottom, left:right] = True
return mask
# Calculate the path between the two points
dx = x2 - x1
dy = y2 - y1
numSteps = max(abs(x2 - x1), abs(y2 - y1))
if numSteps == 0:
numSteps = 1
dx = float((x2 - x1)) / numSteps
dy = float((y2 - y1)) / numSteps
for i in range(numSteps):
x = int(x1 + (i * dx))
y = int(y1 + (i * dy))
top, bottom, left, right = self.calcBlobCoords(y, x)
mask[top:bottom, left:right] = True
return mask
def getBPGridCell(self, bpName):
"""
Returns the GFE grid cell corresponding to the specified breakpoint
"""
for bpType in self._bpLatLonDict:
for gridCell in self._bpLatLonDict[bpType]:
name, lat, lon = self._bpLatLonDict[bpType][gridCell]
if name == bpName:
return gridCell
print("****** Grid cell not found for:", bpName)
return None
def getGridCellSequence(self, bpList):
"""
Returns a sequence of grid coordinates that match the format and
location of the specified sequence of breakpoints.
"""
gridCellList = []
delimiter = " - "
parsedBPList = []
for bp in bpList:
if delimiter in bp:
parts = bp.split(delimiter)
for p in parts:
if p not in parsedBPList:
parsedBPList.append(p)
else:
if bp not in parsedBPList:
parsedBPList.append(bp)
segmentList = self.makeBPSegments(parsedBPList)
for segment in segmentList:
if delimiter in segment: # two breakpoints
parts = segment.split(delimiter)
bp0 = self.getBPGridCell(parts[0])
bp1 = self.getBPGridCell(parts[1])
if not (bp0 and bp1):
continue
cellTuple = (bp0, bp1)
else:
bp = self.getBPGridCell(segment)
if not bp:
continue
cellTuple = (bp)
gridCellList.append(cellTuple)
return gridCellList
# Updates the GFE spatial display based on the specified list of BP names
def updateDisplay(self, plotAllStorms = False):
"""
Update the spatial GFE display based on the current state of the
stormInfo data.
"""
hazKeys = self._hazardOrder
self._bpHazGrid = self.empty(np.int8)
grid = self.empty(np.int8)
# Define the advisory list based on specified flag
if plotAllStorms:
advisoryList = list(self._stormInfoDict.keys())
else:
advisoryList = [self._selectedAdvisory]
for advisory in advisoryList:
if advisory == "":
continue
if "Breakpoints" not in self._stormInfoDict[advisory]:
continue
stormInfoBPKeys = self._stormInfoDict[advisory]["Breakpoints"].keys()
for hazard in self._hazardOrder:
if hazard not in stormInfoBPKeys:
continue
mask = self.empty(np.bool)
bpList = self._stormInfoDict[advisory]["Breakpoints"][hazard]
gridCellList = self.getGridCellSequence(bpList)
for points in gridCellList:
if isinstance(points[0], tuple):
mask |= self.getPathMask(points[0][0], points[0][1],
points[1][0], points[1][1])
else:
mask |= self.getPathMask(points[0], points[1])
hazIndex = hazKeys.index(hazard)
grid[mask] = hazIndex
# Create the grid showing the breakpoint areas
weName = "BreakpointHazards"
self.createGrid(self.mutableID(), weName, "DISCRETE", (grid, hazKeys), self._timeRange,
defaultColorTable="Hazards", discreteKeys=hazKeys,
discreteOverlap=1, discreteAuxDataLength=5)
if not self._selectedAdvisory:
return
# Plot the storm number label
stormNum = self._stormInfoDict[self._selectedAdvisory]["stormNumber"]
labelText = "Storm\nNumber\n" + str(stormNum)
self._stormLabel.config(text=labelText)
# This is commented out for now as it causes a crash from time to time
self.setActiveElement(self.mutableID(), weName, "SFC", self._timeRange)
def makeStormButton(self, frame, label, row, column, active):
"""
Makes a single storm button. This is implemented as a separate method
so the lambda method works properly.
"""
# Set the button state
state = tk.NORMAL
if not active:
state = tk.DISABLED
# Set the background color
bgColor = self._unselectedColor
if label == self._selectedAdvisory:
bgColor = self._selectedColor
button = tk.Button(frame, text=label, command=lambda: self.stormButtonSelected(label),
font=self._font14Bold, state=state, bg=bgColor, activebackground=bgColor)
button.grid(row=row, column=column, padx=20, pady=5)
return button
def makeHazardButton(self, frame, label, row):
"""
Makes a single hazard button. This is implemented as a separate method
so the lambda method works properly.
"""
button = tk.Button(frame, text=label, command=lambda: self.hazardButtonSelected(label),
font=self._font14Bold, bg=self._colors[label])
button.grid(row=row, padx=20, pady=7)
return button
def stormButtonSelected(self, buttonLabel):
"""
Called when any storm button (advisory) button is selected. Updates the
internal selectedAdvisory variable and updates the display.
"""
if self._selectedAdvisory:
if buttonLabel != self._selectedAdvisory:
self._advisoryButtons[self._selectedAdvisory].config(bg=self._unselectedColor)
self._advisoryButtons[self._selectedAdvisory].config(activebackground=self._unselectedColor)
self._advisoryButtons[buttonLabel].config(bg=self._selectedColor)
self._advisoryButtons[buttonLabel].config(activebackground=self._selectedColor)
self._selectedAdvisory = buttonLabel
self.updateDisplay()
return
def dumpBPs(self):
"""
Utility method to dump the contents of the internal stormInfo
"""
print("-----------StormInfo DUMP-----------------------------------")
adv = self._selectedAdvisory
hazards = self._stormInfoDict[adv]["Breakpoints"]
for haz in hazards:
for bp in self._stormInfoDict[adv]["Breakpoints"][haz]:
print(adv, haz, bp)
print("------------------------------------------------------------")
return
def maskToBreakpoints(self, mask):
"""
Calculates the set of breakpoint that lie inside the specified mask.
"""
# Mask of breakpoints inside the specified mask
selectedBPMask = mask & self._breakpointMask
# The grid coordinates of the above mask
selectedY, selectedX = np.nonzero(selectedBPMask)
bpList = []
for bpType in self._bpLatLonDict:
for bp in zip(selectedY, selectedX):
# See if this point is in this breakpoint type
if bp in self._bpLatLonDict[bpType]: # See if this point is in this breakpoint type
name, lat, lon = self._bpLatLonDict[bpType][bp]
bpList.append(name)
return bpList
def makeBPSegments(self, bpList):
"""
Make breakpoint segments based on the specified bpList. Sorts the list and uses
the ranking algorithm to determine where breakpoint sequences start and stop.
"""
sortedBPs = sorted(bpList, key=lambda x: self.bpRank(x))
rankDict = {}
for bp in sortedBPs:
rankDict[bp] = self.bpRank(bp)
segmentList = []
lastBP = ""
if len(sortedBPs) == 1:
return sortedBPs
for bp in sortedBPs:
# Get the type of BP. Water and island BPs stand on their own
bpTypeNum = int(rankDict[bp] / 1000000)
bpType = self._bpTypes[bpTypeNum - 1]
if bpType in ["island", "water"]:
segmentList.append(bp)
lastBP = bp
continue
if lastBP == "":
lastBP = bp
continue
# It's a land BP so group pairs that are consecutive
if abs(rankDict[lastBP] - rankDict[bp]) == 1: # they're consecutive
segmentName = lastBP + " - " + bp
segmentList.append(segmentName)
lastBP = bp
# Places where gaps form because of the way the breakpoints are defined
# in the *.tbl files.
bpGaps = [("Mouth of the Rio Grande River", "Barra El Mezquital"),
("Samana", "Cabo Engano"),
("Cabo San Antonio", "Artemisa/Pinar del Rio"),
]
# Because of the way that the breakpoints are defined in the land.tbl file
# there are gaps that occur when selecting particular breakpoints that
# span across borders and end points of large islands.
# So, check for this case and add a "phantom" segment so this gap is filled.
for bp1, bp2 in bpGaps:
if bp1 in bpList and bp2 in bpList:
segmentList.append(bp1 + " - " + bp2) # add the segment
return segmentList
def hazardButtonSelected(self, hazard):
"""
Called when the user selects a wind hazard button. Fetches the active area
and figures out the breakpoints within. Converts those to breakpoint segments,
removes them from the stormInfo first and then adds them back to ensure each
segment is assigned to only on hazard. Finally it updates the display.
"""
ea = self.getActiveEditArea()
# Calculate the mask and
mask = self.encodeEditArea(ea)
if not mask.any():
self.statusBarMsg("Please select an edit area before assigning a Hazard.", "S")
return
bpList = self.maskToBreakpoints(mask)
segmentList = self.makeBPSegments(bpList)
# First check to see if any conflict with existing storms
if hazard != "<None>":
if self.anyBreakpointConflicts(segmentList):
return
# Remove any BPs in the existing lists found in with any hazard
self.removeBreakpoints(segmentList)
# Add the new BPs to the list, unless it's None
if hazard != "<None>":
self.addBreakpoints(hazard, segmentList)
self._savingNeeded = True
# Update the display
self.updateDisplay()
return
def makeStormNumLabel(self, frame):
"""
Makes the label to be plotted in the GUI.
"""
stormNumFrame = tk.Frame(frame)
stormNumFrame.grid(row=6, column=0, columnspan=2)
self._stormLabel = tk.Label(stormNumFrame, text="", font=self._font12Normal)
self._stormLabel.grid(row=0, column=0, pady=10)
return
def binButtons(self, siteID):
basins = self._WindWWUtils.forecastBasins(siteID)
return self._WindWWUtils.basinBins(basins)
def makeStormButtons(self, frame):
"""
Makes the advisory buttons on the GUI.
"""
siteID = self.getSiteID()
allButtonLabels = self.binButtons(siteID)
if not allButtonLabels:
return
for column, buttonLabels in enumerate(allButtonLabels):
for i, label in enumerate(buttonLabels):
activeButton = label in self._advisoryNames
row = i
self._advisoryButtons[label] = self.makeStormButton(frame, label, row, column,
activeButton)
return
def makeWindHazardButtons(self, frame):
"""
Makes the wind hazard buttons on the GUI.
"""
buttonLabels = ["HU.W", "HU.A", "TR.W^HU.A", "TR.W", "TR.A", "<None>"]
for i, label in enumerate(buttonLabels):
self.makeHazardButton(frame, label, i)
return
def removeBreakpoints(self, bpList):
"""
Removes the specified breakpoints from the stormInfoDict.
"""
bpDict = self._stormInfoDict[self._selectedAdvisory]["Breakpoints"]
for hazard in self._hazardOrder:
if hazard == "<None>":
continue
for bp in bpList:
if bp in bpDict[hazard]:
# Remove the Breakpoint segment
bpDict[hazard].remove(bp)
return
def addBreakpoints(self, hazard, bpList):
"""
Adds the specified breakpoints to the stormInfoDict.
"""
# Fetch the current set of BPs
currentBPs = self._stormInfoDict[self._selectedAdvisory]["Breakpoints"].get(hazard, None)
if currentBPs is None:
currentBPs = []
# Add the new BPs
for bp in bpList:
if bp in currentBPs:
continue
currentBPs.append(bp)
# Replace the old list with this one.
self._stormInfoDict[self._selectedAdvisory]["Breakpoints"][hazard] = currentBPs
return
def anyBreakpointConflicts(self, addedBPSegments):
"""
Returns True if any of the specified added segments also exist in another storm.
"""
for advisory in self._stormInfoDict:
if advisory == self._selectedAdvisory:
continue
bpDict = self._stormInfoDict[advisory]["Breakpoints"]
for hazard in bpDict:
bpList = bpDict[hazard]
for bp in addedBPSegments:
if bp in bpList:
self.statusBarMsg(bp + " conflicts with breakpoints in advisory: " + advisory + " for hazard: " + hazard, "S")
return True
return False
def displayWindowOnCursor(self, master):
"""
Moves the specified window to the curso location.
"""
master.update_idletasks()
wh= master.winfo_height()
ww= master.winfo_width()
px, py = master.winfo_pointerxy()
master.geometry("%dx%d+%d+%d" % (ww, wh, px - (ww //2 ),py - (wh // 2)))
return
def setUpUI(self):
"""
Makes the tk calls to set up the GUI.
"""
self._tkmaster = tk.Tk()
self._master = tk.Toplevel(self._tkmaster)
self._dialogMaster = None
self._master.title("Select Breakpoint Hazards")
self._master.attributes("-topmost", True)
# Capture the "x" click to close the GUI
self._master.protocol('WM_DELETE_WINDOW', self.cancelCommand)
self._topFrame = tk.Frame(self._master)
self._topFrame.grid()
self._tkmaster.withdraw() # remove the master from the display
self._advisoryButtons = {}
stormFrame = tk.Frame(self._master, relief=tk.GROOVE, bd=3)
stormFrame.grid(row=0, column=0, padx=20, pady=20)
self.makeStormButtons(stormFrame)
self.makeStormNumLabel(stormFrame)
hazFrame = tk.Frame(self._master, relief=tk.GROOVE, bd=3)
hazFrame.grid(row=0, column=1, padx=20, pady=20)
self.makeWindHazardButtons(hazFrame)
# Make the Run, Run/Dismiss, Cancel buttons
bottomButtonFrame = tk.Frame(self._master, relief=tk.GROOVE, bd=3)
bottomButtonFrame.grid(row=5, columnspan=2, pady=20)
self.makeBottomButtons(bottomButtonFrame)
self.updateDisplay()
return
def getAdvisoryNames(self):
"""
Picks the first active advisory for this site.
"""
binList = self.binButtons(self.getSiteID())
nameList =[]
for advisory in self._stormInfoDict:
if advisory in binList:
nameList.append(advisory)
return nameList
def execute(self):
"""
Main method to start the tool.
"""
# set up some constants for this tool
self._font12Normal = "Helvetica 12 normal"
self._font14Bold = "Helvetica 14 bold"
self._bgColor = "#d9d9d9"
self._selectedColor = "green"
self._unselectedColor = "gray80"
# List of tropical wind hazards in increasing order of severity
self._hazardOrder = ["<None>", "TR.A", "HU.A", "TR.W", "TR.W^HU.A", "HU.W"]
self._colors = {"HU.W" : "red",
"HU.A" : "pink",
"TR.W^HU.A" : "purple",
"TR.W" : "#0092FF", # not too dark blue
"TR.A" : "yellow",
"<None>" : 'white'
}
self._bpTypes = ["land", "island", "water",
]
# Path for the breakpoint tables.
path = "/localapps/runtime/RecommendWindWatchWarning/"
self._filePaths = {
"land" : path + "tcabkpt_land.tbl",
"island" : path + "tcabkpt_island.tbl",
"water" : path + "tcabkpt_water.tbl",
}
self._blobSize = 5
# Make a timeRange used for displaying the grid, one day long starting now.
start = int((self._gmtime().unixTime()) / 3600) * 3600 - (6 * 3600)
end = start + 24 * 3600
self._timeRange = TimeRange.TimeRange(AbsTime.AbsTime(start),
AbsTime.AbsTime(end))
# Fetch the storm information from the JOSN files.
self._stormInfoDict = self._WindWWUtils.fetchStormInfo(self._hazardOrder)
# Fetch the active advisory names from the JSON files.
self._advisoryNames = self.getAdvisoryNames()
if not self._advisoryNames:
self.statusBarMsg("No Advisory files found. Please run StormInfo first.", "U")
return
# Set the default selectedAdvisory
self._selectedAdvisory = self._advisoryNames[0]
self._savingNeeded = False
self._countryDict = {}
self._bpLatLonDict, self._countryDict = \
self._WindWWUtils.createBreakpointsDict(self._filePaths)
# Create a mask consisting of the locations of the breakpoints.
# This helps later when we need to find the breakpoints inside
# the selected edit area.
self._breakpointMask = self.empty(np.bool)
for bpDict in self._bpLatLonDict.values():
for gridCell in bpDict:
self._breakpointMask[gridCell] = True
self.setUpUI()
self.displayWindowOnCursor(self._master)
tk.mainloop()
return