awips2/cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/SmartScript.py
2017-04-21 18:33:55 -06:00

2766 lines
119 KiB
Python

##
# This software was developed and / or modified by Raytheon Company,
# pursuant to Contract DG133W-05-CQ-1067 with the US Government.
#
# U.S. EXPORT CONTROLLED TECHNICAL DATA
# This software product contains export-restricted data whose
# export/transfer/disclosure is restricted by U.S. law. Dissemination
# to non-U.S. persons whether in the United States or abroad requires
# an export license or other authorization.
#
# Contractor Name: Raytheon Company
# Contractor Address: 6825 Pine Street, Suite 340
# Mail Stop B8
# Omaha, NE 68106
# 402.291.0100
#
# See the AWIPS II Master Rights File ("Master Rights File.pdf") for
# further licensing information.
##
########################################################################
# 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.
#
# SmartScript -- library of methods for Smart Tools and Procedures
#
# Author: hansen
# ----------------------------------------------------------------------------
#
# SOFTWARE HISTORY
#
# Date Ticket# Engineer Description
# ------------ ---------- ----------- --------------------------
# 01/09/13 DR15626 J. Zeng Add methods
# enableISCsend
# clientISCSendStatus
# manualSendISC_autoMode
# manualSendISC_manualMode
# 01/30/13 1559 dgilling Fix TypeError in
# getGridCellSwath().
# Mar 13, 2013 1791 bsteffen Implement bulk getGrids to
# improve performance.
# Mar 13, 2013 1793 bsteffen Performance improvements for
# TCMWindTool
# Apr 24, 2013 1947 randerso Fix UVToMagDir to work with scalar arguments
# Cleaned up some constants
# Jun 21, 2013 14983 ryu Fixed encodeEditArea() to evaluate query
# when necessary
# Aug 14, 2013 1571 randerso Fixed encodeEditArea() to return astype(numpy.bool8)
# so mask can be used with advanced indexing
# (e.g. grid[mask] = value)
# Oct 07, 2013 2424 randerso remove use of pytz
# Oct 29, 2013 2476 njensen Improved getting wx/discrete keys in _getGridResults
# Oct 31, 2013 2508 randerso Change to use DiscreteGridSlice.getKeys()
# Nov 07, 2013 2476 dgilling Fix _getGridsResult() for retrieving
# Wx/Discrete in First mode.
# Dec 23, 2013 16893 ryu Added unloadWEs() method (created by njensen)
# Apr 29, 2014 3097 randerso Fixed getGrids() to return non-scalar grids as tuples in all cases
# Nov 26, 2014 #633 zhao Corrected a type error in loadParm()
# Dec 01, 2014 3875 randerso Added gmTime() and localTime() functions which are exact equivalents
# to those in the python time module.
# Added getTimeZoneStr and getTzInfo which return the site's local time
# zone as a string or as an object respectively
# Fixed createTimeRange to correctly return time ranges relative to local
# time regardless of setting of os.environ['TZ']
# Jan 13, 2015 3955 randerso Added optional parameter to availableParms to specify desired databases.
# Fixed createGrid to accept a DatabaseID for model
# Apr 23, 2015 4259 njensen Updated for new JEP API
# Jul 17, 2015 4575 njensen callSmartTool() and callProcedure() send HashMap for varDict
# Aug 13, 2015 4704 randerso Added NumpyJavaEnforcer support in createGrids and decodeEditArea
# additional code cleanup
# Aug 26, 2015 4809 randerso Added option group parameter to editAreaList()
# Aug 26, 2015 4804 dgilling Added callTextFormatter().
# Aug 27, 2015 4805 dgilling Added saveCombinationsFile().
# Aug 27, 2015 4806 dgilling Added transmitTextProduct().
# Sep 16, 2015 4871 randerso Return modified varDict from called Tool/Procedure
#
# Sep 11, 2015 4858 dgilling Remove notification processing from publishElements.
# Jan 20, 2016 4751 randerso Fix type of mask returned from getComposite() to work with numpy 1.9.2
# Jan 28, 2016 5129 dgilling Support changes to IFPClient.
# Feb 22, 2016 5374 randerso Added support for sendWFOMessage
# Apr 05, 2016 5539 randerso Added exception when attempting create more than 256 Wx keys
# 05/06/2016 18967 ryu Fix issue of contours plotted over ProposedWatches grid
# when ViewWCL is run.
# Aug 22, 2016 18605 ryu Retrieve operational text product in test mode.
# Sep 28, 2016 19293 randerso Added loadCombinationsFile method. Moved CombinationsFileUtil to
# common.
#
########################################################################
import types, string, time, sys
from math import *
from numpy import *
import os
import numpy
import math
import re
import jep
import BaseTool, Exceptions
import DatabaseID, TimeRange, AbsTime, ParmID
import GridInfo
import JUtil
import NumpyJavaEnforcer
from java.util import ArrayList
from java.util import Date
from java.nio import FloatBuffer
from com.raytheon.uf.common.time import SimulatedTime
from com.raytheon.uf.common.time import TimeRange as javaTimeRange
from com.raytheon.uf.common.dataplugin.gfe.grid import Grid2DByte
from com.raytheon.uf.common.dataplugin.gfe.grid import Grid2DFloat
from com.raytheon.uf.common.dataplugin.gfe.discrete import DiscreteKey
from com.raytheon.uf.common.dataplugin.gfe.discrete import DiscreteKeyDef
from com.raytheon.uf.common.dataplugin.gfe.discrete import DiscreteDefinition
from com.raytheon.uf.common.dataplugin.gfe.weather import WeatherKey
from com.raytheon.uf.common.dataplugin.gfe.db.objects import TimeConstraints
from com.raytheon.uf.common.dataplugin.gfe.db.objects import GridParmInfo
GridType = GridParmInfo.GridType
from com.raytheon.uf.common.dataplugin.gfe.server.request import SendISCRequest
from com.raytheon.uf.common.dataplugin.gfe.textproduct import CombinationsFileUtil
from com.raytheon.viz.gfe.dialogs.formatterlauncher import ConfigData
ProductStateEnum = ConfigData.ProductStateEnum
from com.raytheon.viz.gfe.textformatter import FormatterUtil
from com.raytheon.viz.gfe.textformatter import TextProductFinishWaiter
from com.raytheon.viz.gfe.textformatter import TextProductTransmitter
class SmartScript(BaseTool.BaseTool):
def __init__(self, dataMgr):
BaseTool.BaseTool.__init__(self)
self.__dataMgr = dataMgr
self.__parmMgr = self.__dataMgr.getParmManager()
self.__refSetMgr = self.__dataMgr.getRefManager()
self.__mutableID = DatabaseID.DatabaseID(self.__parmMgr.getMutableDatabase())
self.__cycler = self.__dataMgr.getGridCycler()
self.__parmOp = self.__dataMgr.getParmOp()
# A cache of grids accessed by the derived class
#self.__pythonGrids = []
self.__accessTime = 0
self.__gridLoc = self.__parmMgr.compositeGridLocation()
self.__gridShape = (self.__gridLoc.getNy().intValue(), self.__gridLoc.getNx().intValue())
self.__topoGrid = None
self.__toolType = "numeric"
self._empty = self.empty()
self._minus = self.newGrid(-1)
self._handlers = dict()
def empty(self, dtype=float32):
"""Return a grid filled with 0"""
return zeros(self.getGridShape(), dtype)
def newGrid(self, initialValue, dtype=float32):
"""Return a grid filled with initialValue"""
return full(self.getGridShape(), initialValue, dtype)
##
## Call ProcessVariableList to obtain values from the user
##
## @param VariableList: list() of tuples describing the widgets to display
##
## @return dict() of values gathered from the widgets
##
def getVariableListInputs(self, VariableList):
import ProcessVariableList
return ProcessVariableList.buildWidgetList(VariableList)
def mutableID(self):
# Returns the mutable database ID
return self.__mutableID
def getGridLoc(self):
return self.__gridLoc
def setToolType(self, toolType):
# Tool type is "point-based", "numeric", "parm-based"
# It is set when SmartScript is instantiated.
# For Procedures, it is set to the default of "point-based"
# So a procedure can override this by using this method.
self.__toolType = toolType
def editAreaList(self, eaGroup=None):
"""
Returns a list of strings containing all edit areas in eaGroup.
If eaGroup is None, all known edit areas are returned.
"""
eaList = []
if eaGroup is not None:
eans = self.__refSetMgr.getGroupData(eaGroup)
size = eans.size()
for i in range(size):
eaList.append(str(eans.get(i)))
else:
eans = self.__refSetMgr.getAvailableSets()
size = eans.size()
for i in range(size):
eaList.append(eans.get(i).getName())
return eaList
def getSite4ID(self, id3):
# Returns 4-letter site id, based on 3-letter site id
if id3 in ['SJU']:
return "TJSJ"
elif id3 in ['AFG', 'AJK', 'HFO', 'GUM']:
return "P" + id3
elif id3 in ['AER', 'ALU']:
return "PAFC"
else:
return "K" + id3
def loadedParms(self):
# Returns a list of tuples that are weather elements that are
# loaded. The tuples are (element, level, model). element and
# level are strings. model is a DatabaseID.
allParms = self.__parmMgr.getAllParms()
retList = []
for p in allParms:
pid = p.getParmID()
dbid = DatabaseID.DatabaseID(pid.getDbId())
retList.append((pid.getParmName(), pid.getParmLevel(), dbid))
return retList
def availableParms(self, dbs=None):
# Returns a list of tuples that are weather elements that are
# available in the specified dbs.
# dbs may contain a list of DatabaseIDs or a single DatabaseID
# If dbs is None parms from all available databases are returned.
# The tuples are (element, level, model).
# element and level are strings, model is a DatabaseID.
retList = []
if dbs is None:
dbs = self.__parmMgr.getAvailableDbs()
elif type(dbs) is not list: # assume single db
db = dbs
if isinstance(db, DatabaseID.DatabaseID):
db = db.toJavaObj()
else:
# assume java DatabaseID
pass
dbs = ArrayList()
dbs.add(db)
for i in range(dbs.size()):
d = dbs.get(i);
parms = self.__parmMgr.getAvailableParms(d)
for pid in parms:
dbid = DatabaseID.DatabaseID(pid.getDbId())
retList.append((pid.getParmName(), pid.getParmLevel(), dbid))
return retList
def selectedParms(self):
# Returns a list of tuples that are weather elements that are
# currently selected. The tuples are (element, level, model).
# Element and level are string. model is a DatabaseID.
retList = []
parms = self.__parmMgr.getSelectedParms()
for p in parms:
parmid = p.getParmID()
javaDbId = parmid.getDbId()
dbid = None
if javaDbId is not None:
dbid = DatabaseID.DatabaseID(javaDbId)
retList.append((parmid.getParmName(), parmid.getParmLevel(),
dbid))
return retList
def loadParm(self, model, element, level, mostRecent=0):
# loads a parm and makes it visible.
parm = self.getParm(model, element, level, timeRange=None,
mostRecent=mostRecent)
if parm is not None:
self.__parmMgr.setParmDisplayable(parm, 1)
else:
raise TypeError("SmartScript loadParm: " + \
"couldn't load " + `model` + ' ' + `element` + ' ' + `level` + \
' ' + str(mostRecent) + " (None is returned from getParm())" )
##
# Get the list of timeranges locked by me in this weather element.
#
# @param weName: Weather element to look for locks on
# @type weName: String
# @param level: The level of the element to look for locks on
# @type level: String
# @return: The time ranges
# @rtype: Python list of Python TimeRanges
def lockedByMe(self, weName, level):
# returns list of time ranges locked by me in this weather element
# Uses the mutable database
parm = self.getParm(self.mutableID(), weName, level)
if parm is None:
return []
lt = parm.getLockTable()
jlbm = lt.lockedByMe()
# jlbm is a Java list of Java TimeRanges. Convert it to Python.
jlbmIter = jlbm.iterator()
lbm = []
while (jlbmIter.hasNext()):
jtr = jlbmIter.next()
tr = TimeRange.TimeRange(jtr)
lbm.append(tr)
return lbm
##
# Get the list of timeranges locked by other users in this weather element.
#
# @param weName: Weather element to look for locks on
# @type weName: String
# @param level: The level of the element to look for locks on
# @type level: String
# @return: The time ranges
# @rtype: Python list of Python TimeRanges
def lockedByOther(self, weName, level):
# returns list of time ranges locked by others in this weather element
# Uses the mutable database
parm = self.getParm(self.mutableID(), weName, level)
if parm is None:
return []
lt = parm.getLockTable()
jlbo = lt.lockedByOther()
# jlbo is a Java list of Java TimeRanges. Convert it to Python.
jlboIter = jlbo.iterator()
lbo = []
while (jlboIter.hasNext()):
jtr = jlboIter.next()
tr = TimeRange.TimeRange(jtr)
lbo.append(tr)
return lbo
def forceLock(self, weName, level, startT, endT):
# forces locks in the given time range (startT to endT).
# startT, endT can either be ints/floats, or should be AbsTimes
# Returns 0 if not successful, 1 for okay.
if (type(startT) is types.IntType or type(startT) is types.FloatType) \
and (type(endT) is types.IntType or type(endT) is types.FloatType):
t1 = AbsTime.AbsTime(int(startT))
t2 = AbsTime.AbsTime(int(endT))
tr = TimeRange.TimeRange(t1, t2)
else:
tr = TimeRange.TimeRange(startT, endT) #AbsTime
parm = self.getParm(self.mutableID(), weName, level)
if parm is None:
return 0
else:
return parm.forceLockTR(tr.toJavaObj())
def vtecActiveTable(self):
#returns the VTEC active table (or specified table)
import ActiveTableVtec
entries = self.__dataMgr.getActiveTable()
try:
return ActiveTableVtec.transformActiveTableToPython(entries)
except:
raise TypeError("SmartScript vtecActiveTable: could not convert to python objects.")
def gfeOperatingMode(self):
#returns the current operating mode of the GFE.
#Standard, PRACTICE, TEST
return self.__dataMgr.getOpMode().name()
#------------------------------------------------------------------------
# ISC control functions
#------------------------------------------------------------------------
def enableISCsend(self, state):
#sets the overall isc send state. If the send state is false, then
#no ISC grids can be transmitted. To change the behavior
#when these programs (e.g., procedures) are run from the command line,
#you can enable/disable the send capability upon saving. This
#command does not send grids, but sets the system state. When
#saving grids and SendISCOnSave is set, or the manual Send ISC Dialog
#is used, then the grids will be sent.
self.__dataMgr.enableISCsend(state)
def clientISCSendStatus(self):
#returns the current state for sending isc from this program. This
#depicts the state of whether this client has been enabled to send
#ISC via the SendISCOnSave or manual Send ISC Dialog. The ifpServer
#still needs to be properly configured for sending to occur.
return self.__dataMgr.clientISCSendStatus()
def manualSendISC_autoMode(self):
#Simulates the use of the SendISCDialog. Note if the ifpServer's
#SendISCOnSave is enabled, then this routine will fail as grids are
#sent when saved and the manual operation is not allowed. The
#overall isc send state must also be True for this command to work.
req = ArrayList()
req.add(SendISCRequest())
self.__parmOp.sendISC(req)
def manualSendISC_manualMode(self, requests):
#simulates the use of the SendISCDialog. Note if the ifpServers's
#SendISCOnSave is enabled, then this routine will fail as grids are
#sent when saved and the manual operation is not allowed.
#The requests are tuples of (parmName, parmLevel, timeRange). The
#TimeRange is an TimeRange() instance. The overall isc
#send state must also be True for this command to work.
req = ArrayList()
for parmName, parmLevel, tr in requests:
pid = ParmID.ParmID(name=parmName, dbid=self.mutableID(), level=parmLevel).toJavaObj()
req.add(SendISCRequest(pid, tr.toJavaObj()))
self.__parmOp.sendISC(req)
#########################################################################
## Smart Tool methods ##
#########################################################################
# Arguments
# The following arguments are used throughout the
# SmartScript Library methods
#
# self: When you call a method, use the "self" prefix (see
# examples below)
# model: There are various ways to specify the database model
# from which you want the values:
# -- Simply "Fcst" or "Official" OR
# -- siteID_type_model_modeltime
# where the "type" is an empty string for Standard GFE data
# and is "D2D" for D2D data.
# Examples:
# BOU__NAM12_Mar2912 :gets March 29 12Z NAM12 run created by GFE.
# BOU_D2D_NAM12_Mar2912 :gets March 29 12Z original NAM12 run from D2D.
# If you omit the "modeltime", the most recent model run will
# be selected. For example:
# BOU__NAM12 : gets the most recent NAM12 run created by GFE.
# BOU_D2D_NAM12 : gets the most recent original NAM12 run from D2D.
# -- the result of soliciting a model from the user using the
# "model" or "D2D_model" type of VariableList entry. (See
# examples above.)
# -- you may also use a DatabaseID (see getDatabase, below)
# -- simple string with no special characters (this will be
# assumed to be a model created "on-the-fly"
# element: The element name in quotes:
# e.g. "QPF", "rh", "tp"
# level: The level in quotes:
# e.g. "SFC", "MB350", "BL030"
# x, y: integer coordinates
# timeRange: Must be a special time range object such as
# that passed in the argument list as GridTimeRange or a list of time
# range objects. If it is a list than the return value will be a dict
# where the time range objects are keys and the result of getGrids
# for each time range is the value.
# mode: specifies how to handle the situation if multiple grids
# are found within the given time range:
# "TimeWtAverage": return time-weighted Average value
# "Average" : return Average values
# "Max" : return Max values
# "Min" : return Min values
# "Sum" : return Summed values
# "First" : return values from grid with earliest time range
# "List" : return list of grids (or values for getValue)
# noDataError: If 1, and there is no data, the Smart Tool will abort.
# Otherwise, return None. None is a special variable in Python
# which can be tested as follows:
# PoP = self.getGrid("Fcst", "PoP", "SFC", GridTimeRange,
# noDataError=0)
# if PoP is None:
# print "No data found for PoP"
# mostRecentModel: Applies only to model data. Will get the
# most recent model and ignore any times (if included) in the
# model argument. (Note that if a time is not included in the
# model argument, you will automatically get the most recent
# model no matter how this argument is set.)
###########################
## Grid Access methods
def getGrids(self, model, element, level, timeRange,
mode="TimeWtAverage",
noDataError=1, mostRecentModel=0,
cache=1):
# Get the value(s) for the given model, element, and level
# at the x, y coordinate and over the given timeRange.
#
# The resulting grid values can be accessed as follows:
# PoPGrid = self.getGrids("Fcst","PoP","SFC", GridTimeRange)
# popValue = PoPGrid[x][y]
#
# where x and y are integer grid coordinates.
#
# The argument descriptions are given above
if isinstance(model, DatabaseID.DatabaseID):
model = model.modelIdentifier()
timeRangeList = None
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
elif isinstance(timeRange, list):
timeRangeList = timeRange
timeRangeArray = jep.jarray(len(timeRangeList), javaTimeRange)
for i in xrange(len(timeRangeList)):
tr = timeRangeList[i]
if isinstance(tr, TimeRange.TimeRange):
tr = tr.toJavaObj()
timeRangeArray[i] = tr
timeRange = timeRangeArray
# if cache:
# for cModel, cElement, cLevel, cMostRecent, cRange, cMode, cResult in \
# self.__pythonGrids:
# if cModel == model and cElement == element and \
# cLevel == level and cRange == timeRange \
# and cMode == mode and cMostRecent == mostRecentModel:
# return cResult
# Get the parm from parmMgr, find the corresponding result
exprName = self.getExprName(model, element, level, mostRecentModel)
parm = self.__parmMgr.getParmInExpr(exprName, 1)
if parm is None:
if noDataError == 1:
raise Exceptions.EditActionError(
"NoData", "No Weather Element for " + exprName)
else:
return None
result = self.__cycler.getCorrespondingResult(parm, timeRange, mode)
if timeRangeList is not None:
retVal = {}
for i in xrange(len(timeRangeList)):
iresult = self._getGridsResult(timeRangeList[i], noDataError, mode, exprName, result[i])
retVal[timeRangeList[i]] = iresult
return retVal
else:
return self._getGridsResult(timeRange, noDataError, mode, exprName, result)
def _getGridsResult(self, timeRange, noDataError, mode, exprName, result):
retVal = None
if result is not None:
if len(result) == 0:
retVal = None
elif "List" == mode:
xlated = []
for rgrid in result:
jxlgrid = rgrid.getGridSlice()
xlgrid = jxlgrid.getNDArray()
if type(xlgrid) is ndarray and xlgrid.dtype == numpy.int8:
# discrete or weather
keys = JUtil.javaObjToPyVal(jxlgrid.getKeyList())
xlgrid = (xlgrid, keys)
elif type(xlgrid) is not numpy.ndarray and len(xlgrid) == 2:
# vector
xlgrid = tuple(xlgrid)
xlated.append(xlgrid)
retVal = xlated
else:
result = result[0];
slice = result.getGridSlice()
retVal = slice.getNDArray()
if type(retVal) is ndarray and retVal.dtype == numpy.int8:
# discrete or weather
keys = JUtil.javaObjToPyVal(slice.getKeyList())
retVal = (retVal, keys)
elif type(retVal) is not numpy.ndarray and len(retVal) == 2:
# vector
retVal = tuple(retVal)
if retVal is None or retVal == []:
if noDataError == 1:
msg = "No corresponding grids for " + exprName + " " + str(timeRange)
raise UserWarning(msg)
# else:
# self.__pythonGrids.append((model, element, level, mostRecentModel,
# timeRange, mode, retVal))
return retVal
# Returns history info for the specified model, element, level and
# timerange. ISC grids force this to be a list of lists [[]].
def getGridHistory(self, model, element, level, timeRange):
if isinstance(model, DatabaseID.DatabaseID):
model = model.modelIdentifier()
exprName = self.getExprName(model, element, level)
parm = self.__parmMgr.getParmInExpr(exprName, 1)
if parm is None:
raise Exceptions.EditActionError(
"NoData", "getGridInfo: No Weather Element " + exprName)
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
grids = parm.getGridInventory(timeRange)
if len(grids) == 0:
return []
historyList = []
for grid in grids:
history = grid.getHistory()
histList = []
for h in history:
histList.append((str(h.getOrigin()),
ParmID.ParmID(jParmId=h.getOriginParm()),
TimeRange.TimeRange(h.getOriginTimeRange()),
AbsTime.AbsTime(h.getTimeModified()),
str(h.getWhoModified()),
AbsTime.AbsTime(h.getUpdateTime()),
AbsTime.AbsTime(h.getPublishTime())))
historyList.append(histList)
return historyList
def taperGrid(self, editArea, taperFactor=5):
# Returns a 2-D Grid of values between 0-1 about the
# given edit area.
# These values can be applied by smart tools to taper results.
# Argument:
# editArea : must be of type AFPS.ReferenceData or None
# (use editArea tool argument)
# taperFactor: If set to zero, will do Full Taper
# Example:
# def preProcessTool(self, editArea):
# self._tGrid = self.taperGrid(editArea, 5)
# def execute(self, variableElement):
# return = variableElement + self._tGrid * 10.0
#
taperGrid = self.__refSetMgr.taperGrid(editArea, taperFactor)
taperGrid = taperGrid.getNDArray()
return taperGrid
def directionTaperGrid(self, editArea, direction):
# Returns a 2-D Grid of values between 0-1 within the
# given edit area.
# E.g. if the Dir is W and x,y is half-way along the
# W to E vector within the given edit area, the value of
# directionTaperGrid at x,y will be .5
# These values can be applied by smart tools to show
# spatial progress across an edit area.
# Argument:
# editArea : must be of type AFPS.ReferenceData or None
# (use editArea tool argument)
# direction : 16 point text direction e.g. "NNW", "NW", etc.
# Example:
# def preProcessTool(self, editArea):
# self._spaceProgress = self.directionTaperGrid(editArea, "NW")
# def execute(self, variableElement):
# return variableElement * self._spaceProgress
#
taperGrid = self.__refSetMgr.directionTaperGrid(editArea, direction)
taperGrid = taperGrid.getNDArray()
return taperGrid
def getComposite(self, WEname, GridTimeRange, exactMatch=1, onlyISC=0):
# Returns a composite grid consisting of the primary grid and any
# corresponding ISC grid, blended together based on the mask information
# derived from the Grid Data History. Primary grid must exist. Returns
# the set of points that are valid in the output grid. (Note the output
# grid consists of the primary grid and isc grid. Any "invalid" points,
# indicate those areas that have no isc data and are outside the home
# site's region. The returned grid will have the primary data in
# the site's region.)
#
# A Python tuple is returned.
# For Scalar elements, the tuple contains:
# a numeric grid of 1's and 0's where 1 indicates a valid point
# a numeric grid of scalar values
# For Vector elements, the tuple contains:
# a numeric grid of 1's and 0's where 1 indicates a valid point
# a numeric grid of scalar values representing magnitude
# a numeric grid of scalar values representing direction
# For Weather elements, the tuple contains:
# a numeric grid of 1's and 0's where 1 indicates a valid point
# a numeric grid of byte values representing the weather value
# list of keys corresponding to the weather values
#
# For example:
# isc = self.getComposite(WEname, GridTimeRange)
# if isc is None:
# self.noData()
# # See if we are working with a Scalar or Vector element
# wxType = variableElement_GridInfo.type()
# if wxType == 0: # SCALAR
# bits, values = isc
# elif wxType == 1: # VECTOR
# bits, mag, dir = isc
if onlyISC == 0:
exprName = self.getExprName("Fcst", WEname, "SFC")
else:
exprName = self.getExprName("ISC", WEname, "SFC")
parm = self.__parmMgr.getParmInExpr(exprName, 1)
if parm is None:
return None
seTime = AbsTime.AbsTime(self.__dataMgr.getSpatialDisplayManager().getSpatialEditorTime())
if GridTimeRange.contains(seTime):
gridTime = seTime
else:
gridTime = GridTimeRange.startTime()
from com.raytheon.viz.gfe.edittool import GridID
gid = GridID(parm, gridTime.javaDate())
wxType = self.__dataMgr.getClient().getPythonClient().getGridParmInfo(parm.getParmID()).getGridType()
if GridType.SCALAR.equals(wxType):
from com.raytheon.uf.common.dataplugin.gfe.slice import ScalarGridSlice
slice = ScalarGridSlice()
bits = self.__dataMgr.getIscDataAccess().getCompositeGrid(gid, exactMatch, slice)
args = (bits.getNDArray().astype(bool), slice.getScalarGrid().getNDArray())
elif GridType.VECTOR.equals(wxType):
from com.raytheon.uf.common.dataplugin.gfe.slice import VectorGridSlice
slice = VectorGridSlice()
bits = self.__dataMgr.getIscDataAccess().getVectorCompositeGrid(gid, exactMatch, slice)
args = (bits.getNDArray().astype(bool), slice.getMagGrid().getNDArray(), slice.getDirGrid().getNDArray())
elif GridType.WEATHER.equals(wxType):
from com.raytheon.uf.common.dataplugin.gfe.slice import WeatherGridSlice
slice = WeatherGridSlice()
bits = self.__dataMgr.getIscDataAccess().getCompositeGrid(gid, exactMatch, slice)
keys = []
for k in slice.getKeys():
keys.append(str(k))
args = (bits.getNDArray().astype(bool), slice.getWeatherGrid().getNDArray(), keys)
elif GridType.DISCRETE.equals(wxType):
from com.raytheon.uf.common.dataplugin.gfe.slice import DiscreteGridSlice
slice = DiscreteGridSlice()
bits = self.__dataMgr.getIscDataAccess().getCompositeGrid(gid, exactMatch, slice)
keys = []
for k in slice.getKeys():
keys.append(str(k))
args = (bits.getNDArray().astype(bool), slice.getDiscreteGrid().getNDArray(), keys)
return args
##
# Return the GridInfo object for the given weather element and timeRange
# Example:
# timeRange = self.getTimeRange("Today")
# infoList = self.getGridInfo("Fcst", "T", "SFC", timeRange)
# for info in infoList:
# print "grid", info.gridTime()
#
# @param model: The model for which grid info is requested.
# @type model: DatabaseId or String
# @param element: The element for which grid info is requested.
# @type element: String
# @param level: The level for which grid info is requested.
# @type level: String
# @param timeRange: A time range over which grid info is requested.
# @type timeRange: com.raytheon.uf.common.time.TimeRange or TimeRange
# @param mostRecentModel: whether to use current time in request expr.
# @type mostRecentModel: integer or boolean
# @return: Java GridParmInfo object
def getGridInfo(self, model, element, level, timeRange,
mostRecentModel=0):
if isinstance(model, DatabaseID.DatabaseID):
model = model.modelIdentifier()
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
parm = self.getParm(model, element, level, mostRecentModel)
if parm is None:
exprName = self.getExprName(model, element, level, mostRecentModel)
raise Exceptions.EditActionError(
"NoData", "getGridInfo: No Weather Element " + exprName)
grids = parm.getGridInventory(timeRange)
if len(grids) == 0:
return []
gridParmInfo = parm.getGridInfo()
gridInfos = []
for grid in grids:
timeRange = grid.getGridTime()
gridInfo = GridInfo.GridInfo(gridParmInfo=gridParmInfo,
gridTime=timeRange)
gridInfos.append(gridInfo)
return gridInfos
###########################
## Sounding methods
# Numeric only
def makeNumericSounding(self, model, element, levels, timeRange,
noDataError=1, mostRecentModel=0):
# Make a numeric sounding for the given model, element, and levels
# Example:
# levels = ["MB850","MB800","MB750","MB700","MB650","MB600"]
# gh_Cube, rh_Cube = self.makeNumericSounding(
# model, "rh", levels, GridTimeRange)
#
# Arguments:
#
# The "levels" argument is a Python list of levels INCREASING
# in height.
# This method returns two numeric cubes:
# ghCube of geopotential heights for the given levels
# valueCube of values for the given levels
ghCube = []
valueCube = []
magCube = []
dirCube = []
for level in levels:
ghGrids = self.getGrids(model, "gh", level, timeRange,
noDataError=noDataError,
mostRecentModel=mostRecentModel)
if ghGrids is None:
return None
valueGrids = self.getGrids(model, element, level, timeRange,
noDataError=noDataError,
mostRecentModel=mostRecentModel)
if valueGrids is None:
return None
if type(ghGrids) == types.ListType:
ghGrid = ghGrids[0]
else:
ghGrid = ghGrids
if type(valueGrids) == types.ListType:
valueGrid = valueGrids[0]
else:
valueGrid = valueGrids
#jdynina ghCube = ghCube + [ghGrid]
ghCube.append(ghGrid)
if type(valueGrid) == types.TupleType:
magCube = magCube + [valueGrid[0]]
dirCube = dirCube + [valueGrid[1]]
else:
valueCube = valueCube + [valueGrid]
ghCube = array(ghCube)
if len(magCube) > 0:
magCube = array(magCube)
dirCube = array(dirCube)
valueCube = (magCube, dirCube)
else:
valueCube = array(valueCube)
return (ghCube, valueCube)
# numeric only
def getNumericMeanValue(self, model, element, levels, timeRange,
noDataError=1):
# Return a numeric array of mean values for the given element
# between and including the given levels
if len(levels) < 1:
return self.errorReturn(
noDataError,
"SmartScript.getNumericMeanValue:: No Levels for Mean Value.")
elementType = "Scalar"
empty = self.getTopo() * 0.0
totalValue = empty
uSum = empty
vSum = empty
for level in levels:
value = self.getGrids(model, element, level, timeRange,
noDataError=noDataError)
if type(value) == types.TupleType:
elementType = "Vector"
uw, vw = self.MagDirToUV(value[0], value[1])
uSum = uSum + uw
vSum = vSum + vw
else:
totalValue = totalValue + value
# Compute the average
totCount = float(len(levels))
if elementType == "Scalar":
return totalValue / totCount
else:
u = uSum / totCount
v = vSum / totCount
mag, dir = self.UVToMagDir(u, v)
mag = int(mag + 0.5)
dir = int(dir + 0.5)
return (mag, dir)
###########################
## Conversion methods
def UVToMagDir(self, u, v):
RAD_TO_DEG = 180.0 / numpy.pi
# Sign change to make math to meteor. coords work
u = -u
v = -v
if type(u) is numpy.ndarray or type(v) is numpy.ndarray:
speed = numpy.sqrt(u * u + v * v)
dir = numpy.arctan2(u, v) * RAD_TO_DEG
dir[numpy.greater_equal(dir, 360)] -= 360
dir[numpy.less(dir, 0)] += 360
else:
speed = math.sqrt(u * u + v * v)
dir = math.atan2(u, v) * RAD_TO_DEG
while dir < 0.0:
dir = dir + 360.0
while dir >= 360.0:
dir = dir - 360.0
return (speed, dir)
def MagDirToUV(self, mag, dir):
DEG_TO_RAD = numpy.pi / 180.0
# Note sign change for components so math to meteor. coords works
uw = - sin(dir * DEG_TO_RAD) * mag
vw = - cos(dir * DEG_TO_RAD) * mag
return (uw, vw)
def convertMsecToKts(self, value_Msec):
# Convert from meters/sec to Kts
return value_Msec * 3600.0 / 1852.0
def convertKtoF(self, t_K):
# Convert the temperature from Kelvin to Fahrenheit
# Degrees Fahrenheit = (Degrees Kelvin - 273.15) / (5/9) + 32
t_F = (t_K - 273.15) * 9.0 / 5.0 + 32.0
return t_F
def KtoF(self, t_K):
return self.convertKtoF(t_K)
def convertFtoK(self, t_F):
# Convert the temperature from Kelvin to Fahrenheit
# Degrees Kelvin = (Degrees Fahrenheit - 32) * (5 / 9) + 273.15
t_K = (t_F - 32.0) * (5.0 / 9.0) + 273.15;
return t_K
def FtoK(self, t_F):
return self.convertFtoK(t_F)
def convertFtToM(self, value_Ft):
# Convert the value in Feet to Meters
return value_Ft * 0.3048
#########################################################################
## Error Handling ##
#########################################################################
def abort(self, info):
# This call will send the info to the GFE status bar,
# put up a dialog with the given info, and abort the
# smart tool or procedure.
# Example:
# self.abort("Error processing my tool")
#
raise TypeError, info
def noData(self, info="Insufficient Data to run Tool"):
# Raise the NoData exception error
raise Exceptions.EditActionError("NoData", info)
def cancel(self):
# Cancels a smart tool without displaying an error message
raise Exceptions.EditActionError("Cancel", "Cancel")
def errorReturn(self, noDataError, message):
if noDataError == 1:
self.abort(message)
else:
return None
##
# Sends the text message to the GFE status bar with the
# given status code: "R" (regular), "S" (significant), "U" (urgent),
# or "A" (alert)
# Example:
# self.statusBarMsg("Running Smart Tool", "R")
#
# @param message: The message to send.
# @type message: string
# @param status: Importance of message. "A"=Alert, "R"=Regular, "U"=Urgent;
# anything else=Significant
# @type status: string
# @param category: The message category. Defaults to "GFE".
# @type category: string
# @return: None
def statusBarMsg(self, message, status, category="GFE"):
from com.raytheon.uf.common.status import UFStatus
Priority = UFStatus.Priority
if "A" == status:
importance = Priority.PROBLEM
elif "R" == status:
importance = Priority.EVENTA
elif "U" == status:
importance = Priority.CRITICAL
else:
importance = Priority.SIGNIFICANT
if category not in self._handlers:
self._handlers[category] = UFStatus.getHandler("GFE", category, 'GFE')
self._handlers[category].handle(importance, message);
#########################
## Smart Commands
##
## These commands take some similar arguments:
## editArea : must be of type AFPS.ReferenceData or None
## (See getEditArea)
## If you specify None, the system will supply
## the active edit area from the GFE or from
## the editArea argument for runProcedure.
## timeRange: must be of type AFPS.TimeRange or None
## (See getTimeRange and createTimeRange)
## If you specify None, the system will supply
## the selected Time Range from the GFE or from
## the timeRange argument for runProcedure.
## varDict : If you supply a varDict in this call, the
## variable list dialog will not be displayed
## when the tool is run.
## If you supply a varDict from a Procedure,
## make sure that the variables
## for all the tools called by the Procedure are
## supplied in your varDict.
## missingDataMode: Can be "Stop", "Skip", or "Create". If not
## included, will be set to the current GFE default.
## modal: If 0, VariableList dialogs will appear with the
## non-modal "Run" and "Run/Dismiss" buttons.
## Otherwise, they will appear with the "Ok" button.
##
## If editValues is true, the grid values are changed.
## FOR POINT-BASED TOOLS ONLY:
## If calcArea is true, a reference area is created and saved which
## shows discrepancies greater than the DiscrepancyValue between the current
## value and new value.
## If calcGrid is true, a scalar grid is created which shows the discrepancy
## amount between the current value and new value. (Not implemented.)
##
## These commands all return an error which will be None if no
## errors occurred. Otherwise, the errorType and errorInfo
## can be accessed e.g. error.errorType() and error.errorInfo()
## If "noData" has been called, the errorType will be "NoData" and
## can be tested by the calling tool or script.
def callSmartTool(self, toolName, elementName, editArea=None,
timeRange=None, varDict=None,
editValues=1, calcArea=0, calcGrid=0,
passErrors=[],
missingDataMode="",
modal=1):
# passErrors: a list of errors to ignore and pass back to the
# calling program. Some errors that can be ignored are:
# NoData
# NoElementToEdit
# ExecuteOrClassError
# LockedGridError
#
# For example:
# In the Procedure:
# error = self.callSmartTool(
# "MyTool", "MixHgt", editArea, timeRange, varDict,
# passErrors= ["NoData"])
# if error is not None:
# print "No Data available to run tool"
#
# In the Smart Tool:
# mixHgt = self.getGrids(model, "MixHgt", "SFC", timeRange)
# if mixHgt is None:
# self.noData()
if editArea is None or not editArea.getGrid().isAnyBitsSet():
editArea = self.__refSetMgr.fullRefSet()
emptyEditAreaFlag = True
else:
emptyEditAreaFlag = False
javaDict = None
if varDict is not None:
javaDict = JUtil.pyValToJavaObj(varDict)
parm = self.getParm(self.__mutableID, elementName, "SFC")
if timeRange is None:
from com.raytheon.viz.gfe.core.parm import ParmState
timeRange = parm.getParmState().getSelectedTimeRange()
else:
timeRange = timeRange.toJavaObj()
from com.raytheon.viz.gfe.smarttool import SmartUtil
result, returnedDict = SmartUtil.callFromSmartScript(self.__dataMgr, toolName, elementName, editArea,
timeRange, javaDict, emptyEditAreaFlag,
JUtil.pylistToJavaStringList(passErrors),
missingDataMode, parm)
if varDict is not None and returnedDict:
returnedDict = JUtil.javaObjToPyVal(returnedDict)
varDict.clear()
varDict.update(returnedDict)
if result:
raise Exceptions.EditActionError(errorType="Error", errorInfo=str(result))
return None
def callProcedure(self, name, editArea=None, timeRange=None, varDict=None,
missingDataMode="Stop",
modal=1):
if editArea is None:
from com.raytheon.uf.common.dataplugin.gfe.reference import ReferenceData
editArea = ReferenceData()
if timeRange is None:
from com.raytheon.uf.common.time import TimeRange as JavaTimeRange
timeRange = JavaTimeRange()
else:
timeRange = timeRange.toJavaObj()
javaDict=None
if varDict is not None:
javaDict = JUtil.pyValToJavaObj(varDict)
from com.raytheon.viz.gfe.procedures import ProcedureUtil
result, returnedDict = ProcedureUtil.callFromSmartScript(self.__dataMgr, name, editArea, timeRange, javaDict)
if varDict is not None and returnedDict:
returnedDict = JUtil.javaObjToPyVal(returnedDict)
varDict.clear()
varDict.update(returnedDict)
# callSmartTool raises the exception put here it is returned.
if result:
return Exceptions.EditActionError(errorType="Error", errorInfo=str(result))
return None
###########################
## Creating On-the-fly elements
def createGrid(self, model, element, elementType, numericGrid, timeRange,
descriptiveName=None, timeConstraints=None,
precision=None, minAllowedValue=None,
maxAllowedValue=None, units=None, rateParm=0,
discreteKeys=None, discreteOverlap=None,
discreteAuxDataLength=None, defaultColorTable=None):
# Creates a grid for the given model and element.
# If the model and element do not already exist, creates them on-the-fly
#
# The descriptiveName, timeConstraints, precision, minAllowedValue,
# maxAllowedValue, units, rateParm, discreteKeys, discreteOverlap,
# and discreteAuxDataLength only need to be
# specified for the first grid being created. These
# values are ignored for subsequent calls to createGrid() for
# the same weather element.
# For new parms, the defaultColorTable is the one to be used for
# display. If not specified and not in the gfe configuration file,
# a DEFAULT color table will be used.
# DISCRETE elements require a definition for discreteKeys,
# discreteAuxDataLength, and discreteOverlap. For DISCRETE, the
# precision, minAllowedValue, maxAllowedValue, and rateParm
# are ignored.
# Note that this works for numeric grids only.
# The arguments exampleModel, exampleElement, and exampleLevel can be
# supplied so that the new element will have the same characteristics
# (units, precision, etc.) as the example element.
#
# model -- If you are creating an "on-the-fly" element (i.e. not
# in the server), this should be a simple string with
# with no special characters. The site ID and other
# information will be added for you.
# If you are creating a grid for a model that exists
# in the server, follow the guidelines for the model
# argument described for the "getValue" command.
# element -- This should be a simple string with no special
# characters.
# elementType -- "SCALAR", "VECTOR", "WEATHER", or "DISCRETE"
# numericGrid -- a Numeric Python grid
# timeRange -- valid time range for the grid. You may want
# to use the "createTimeRange" command
#
# The descriptiveName, timeConstraints, precision, minAllowedValue,
# precision, minAllowedValue, maxAllowedValue, and units can be
# used to define the GridParmInfo needed. Note that timeConstraints
# is not the C++ version, but a (startSec, repeatSec, durSec).
#
# Example:
# self.createGrid("ISCDisc", WEname+"Disc", "SCALAR", maxDisc,
# GridTimeRange, descriptiveName=WEname+"Disc")
#
if string.find(element, "_") >= 0:
message = "SmartScript:createGrid --" + \
"Illegal element name contains underscore. " + \
"No special characters allowed. "
self.abort(message)
parm = self.getParm(model, element, "SFC")
if parm is None:
# Create a parm on-the-fly
# Parm ID
siteID = self.__dataMgr.getSiteID()
if model == "Fcst":
dbi = self.__mutableID
elif isinstance(model, DatabaseID.DatabaseID):
dbi = model
else:
dbi = DatabaseID.databaseID(siteID + "_GRID__" + model + "_00000000_0000")
pid = ParmID.ParmID(element, dbid=dbi).toJavaObj()
# Grid Parm Info set up to use a default at first
if elementType == "VECTOR":
example = self.getParm("Fcst", "Wind", "SFC")
elif elementType == "WEATHER":
example = self.getParm("Fcst", "Wx", "SFC")
elif elementType == "SCALAR":
example = self.getParm("Fcst", "T", "SFC")
elif elementType == "DISCRETE":
example = self.getParm("Fcst", "Hazards", "SFC")
else:
message = "SmartScript:createGrid -- illegal element type"
self.abort(message)
exampleGPI = None
if example is not None:
exampleGPI = example.getGridInfo()
#look for overrides
if descriptiveName is None:
descriptiveName = element
if timeConstraints is None:
if exampleGPI is None:
tc = TimeConstraints(0, 60, 60)
else:
tc = exampleGPI.getTimeConstraints()
elif isinstance(timeConstraints, types.TupleType):
# TC constructor (dur, repeat, start)
# TC tuple (start, repeat, dur)
tc = TimeConstraints(timeConstraints[2], timeConstraints[1],
timeConstraints[0])
else:
# Assume Java TimeConstraints or compatible
tc = TimeConstraints(
timeConstraints.getDuration(), timeConstraints.getRepeatInterval(),
timeConstraints.getStartTime())
if precision is None :
if exampleGPI is None:
precision = 0
else:
precision = exampleGPI.getPrecision()
if maxAllowedValue is None:
if exampleGPI is None:
maxAllowedValue = nanmax(numericGrid)
else:
maxAllowedValue = exampleGPI.getMaxValue()
if minAllowedValue is None:
if exampleGPI is None:
minAllowedValue = nanmin(numericGrid)
else:
minAllowedValue = exampleGPI.getMinValue()
if units is None:
if exampleGPI is None:
units = "1" # unitless
else:
units = exampleGPI.getUnitString()
if tc.anyConstraints() == 0:
timeIndependentParm = 1
timeRange = TimeRange.TimeRange.allTimes().toJavaObj()
else:
timeIndependentParm = 0
#create the new GridParmInfo
minAllowedValue = float(minAllowedValue)
maxAllowedValue = float(maxAllowedValue)
gpi = GridParmInfo(pid,
self.getGridLoc(), GridType.valueOf(elementType), units,
descriptiveName, minAllowedValue, maxAllowedValue,
precision, timeIndependentParm, tc, rateParm)
# if DISCRETE, deal with the key definitions
if elementType == "DISCRETE":
if discreteKeys is None or discreteOverlap is None or \
discreteAuxDataLength is None:
message = "SmartScript:createGrid --" + \
"Discrete elements require discretekeys, " + \
"discreteAuxDataLength, " + \
"and discreteOverlap defined. "
self.abort(message)
currDef = DiscreteKey.discreteDefinition(siteID)
keys = ArrayList()
for h in discreteKeys:
if type(h) is types.TupleType:
kname, kdesc = h
elif type(h) is types.StringType:
kname = h
kdesc = h
keys.add(DiscreteKeyDef(kname, kdesc))
currDef.addDefinition(pid.getCompositeName(), discreteOverlap,
discreteAuxDataLength, keys)
DiscreteKey.setDiscreteDefinition(siteID, currDef)
#set a default color table if specified
if defaultColorTable is not None:
from com.raytheon.viz.gfe import Activator
prefName = element + "_defaultColorTable"
Activator.getDefault().getPreferenceStore().setValue(prefName, defaultColorTable)
#create the parm
parm = self.__parmMgr.createVirtualParm(pid, gpi, None, 1, 1)
# Create Java objects from numericGrid.
# Do this here because, while numericGrid can be sent straight to Java,
# the keys of discrete grids arrive as a single string, which must then
# be parsed. It's easier to create Java objects of the proper types here.
javaGrid = None
auxJavaGrid = None
javaOldKeys = None
if elementType == "DISCRETE" or elementType == "WEATHER":
ngZero = NumpyJavaEnforcer.checkdTypes(numericGrid[0], int8)
dimx = ngZero.shape[1]
dimy = ngZero.shape[0]
# Use createGrid() to get around Jep problems with 3-arg ctor.
javaGrid = Grid2DByte.createGrid(dimx, dimy, ngZero)
oldKeys = numericGrid[1]
javaOldKeys = ArrayList()
for oldKey in oldKeys:
# it seems stupid that we break apart tuples for discrete keys
# when modifying the DiscreteDefinition, but not here when
# creating the actual grid. It actually prevents the grid from
# being created because the string representation of the tuple
# won't match what we added to the DiscreteDefinition.
# However, this is exactly what AWIPS1 does...
# SEE GridCycler.C, line 1131
# FIXME: add oldKey[0] to the ArrayList for tuple types
javaOldKeys.add(str(oldKey))
elif elementType == "SCALAR":
numericGrid = NumpyJavaEnforcer.checkdTypes(numericGrid, float32)
javaGrid = Grid2DFloat.createGrid(numericGrid.shape[1], numericGrid.shape[0], numericGrid)
elif elementType == "VECTOR":
ngZero = NumpyJavaEnforcer.checkdTypes(numericGrid[0], float32)
ngOne = NumpyJavaEnforcer.checkdTypes(numericGrid[1], float32)
javaGrid = Grid2DFloat.createGrid(ngZero.shape[1], ngZero.shape[0], ngZero)
auxJavaGrid = Grid2DFloat.createGrid(ngOne.shape[1], ngOne.shape[0], ngOne)
else:
raise ValueError, "Unknown elementType: %s" % elementType
# Make sure we pass a java TimeRange to Java methods
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
gridData = self.__cycler.makeGridDataFromNumeric(parm, timeRange, javaGrid, auxJavaGrid, javaOldKeys)
parm.replaceGriddedData(timeRange, gridData)
##
#
# @param model: Model name
# @type model: string
# @param element: Element name
# @type element: string
# @param level: Level name
# @type level: string
# @param timeRange: Time range of grid
# @type timeRange: Python or Java TimeRange
# @return: True if grids were deleted
def deleteGrid(self, model, element, level, timeRange):
# Deletes any grids for the given model and element
# completely contained in the given timeRange.
# If the model and element do not exist or if there are no existing grids,
# no action is taken.
#
parm = self.getParm(model, element, level)
if parm is None:
returnVal = False
else:
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
returnVal = parm.deleteTR(timeRange)
return returnVal
def highlightGrids(self, model, element, level, timeRange, color, on=1):
# Highlight the grids in the given time range using designated
# color. If "on" is 0, turn off the highlight.
parm = self.getParm(model, element, level)
from com.raytheon.viz.gfe.core.msgs import HighlightMsg
trs = jep.jarray(1, javaTimeRange)
trs[0] = timeRange.toJavaObj()
HighlightMsg(parm, trs, on, color).send()
def makeHeadlineGrid(self, headlineTable, fcstGrid, headlineGrid=None):
# This method defines a headline grid based on the specified data.
# The headlineTable parameter must be a list of tuples each containing
# the threshold for each headline category and headline label
# Example:
# headlineTable =[(15.0, 'SW.Y'),
# (21.0, 'SC.Y'),
# (34.0, 'GL.W'),
# (47.0, 'SR.W'),
# (67.0, 'HF.W'),
# ]
# "fsctGrid" is the grid that defines what headline category should
# be assigned. "headlineGrid" is the grid you wish to combine with
# the calculated grid. This forces a combine even if the GFE is not
# in combine mode. Omitting "headlineGrid" will cause the calculated
# grid to replace whatever is in the GFE, no matter what the GFE's
# combine mode. Note that a side effect of omitting the headline grid
# is that the GFE will end up in replace mode after the tool completes.
noneKey = "<None>" # define the <None> key
# set the mode to replace so the tool always behaves the same
if headlineGrid is None: # make new headline grid components
headValues = zeros(fcstGrid.shape, int8)
headKeys = [noneKey]
self.setCombineMode("Replace") # force a replace in GFE
else:
headValues, headKeys = headlineGrid
# make sure the headlineTable is not empty
if len(headlineTable) <= 0:
self.statusBarMsg("HeadlineTable is empty", "S")
return headlineGrid
# make a list of (mask, key) for the new headlines
newHeadlines = []
for value, headline in headlineTable:
mask = greater_equal(fcstGrid, value)
if sometrue(mask):
newHeadlines.append((mask, headline))
# make the same list for old headlines
oldHeadlines = []
for i in range(len(headKeys)):
mask = equal(headValues, i)
if sometrue(mask):
oldHeadlines.append((mask, headKeys[i]))
# make combinations at every intersection
for newMask, newKey in newHeadlines:
for oldMask, oldKey in oldHeadlines:
overlap = logical_and(newMask, oldMask) # intersection
if sometrue(overlap): # combined key needed
if oldKey == newKey:
continue
if oldKey == noneKey:
combinedKey = newKey
else:
combinedKey = oldKey + "^" + newKey
# make sure the key is on the list
if combinedKey not in headKeys:
headKeys.append(combinedKey)
index = self.getIndex(combinedKey, headKeys)
headValues[overlap] = index
# return the new headlines grid
return (headValues, headKeys)
######################
## Utility Commands
def findDatabase(self, databaseName, version=0):
# Return an AFPS.DatabaseID object.
# databaseName can have the appended type. E.g. "NAM12" or "D2D_NAM12"
# version is 0 (most recent), -1 (previous), -2, etc.
# E.g.
# databaseID = self.findDatabase("NAM12",0)
# returns most recent NAM12 model
result = self.__parmMgr.findDatabase(databaseName, version)
if result is not None:
result = DatabaseID.DatabaseID(result)
return result
def getDatabase(self, databaseString):
# Return an AFPS.DatabaseID object.
# databaseString is the result of a VariableList entry of type
# "model" or "D2D_model"
dbID = DatabaseID.databaseID(databaseString)
return dbID
def getTimeRange(self, timeRangeName):
# Returns an AFPS.TimeRange object given a time range name
# as defined in the GFE
# E.g.
# timeRange = self.getTimeRange("Today")
tr = self.__dataMgr.getSelectTimeRangeManager().getRange(timeRangeName).toTimeRange();
return TimeRange.TimeRange(tr)
def createTimeRange(self, startHour, endHour, mode="LT", dbID=None):
# Returns an AFPS.TimeRange object given by:
# startHour, endHour
# (range is startHour up to and not including endHour)
# startHour and endHour are relative to midnight of the
# current day either in Local or Zulu time (see below)
# mode can be:
# "LT" : the startHour and endHour are relative to local time
# "Zulu": relative to Zulu time,
# "Database": relative to a database (e.g. model time.
# In this case, the databaseID for the model must
# be supplied (see findDatabase)
#
# E.g.
# timeRange = self.createTimeRange(0,121,"Zulu")
# databaseID = self.findDatabase("NAM12")
# timeRange = self.createTimeRange(120,241,"Database",databaseID)
if mode == "Database" and dbID is None:
raise TypeError("SmartScript createTimeRange: " + \
"Must specify a database ID for mode=Database")
if mode == "LT":
localTime = time.mktime(self.localtime())
gmTime = time.mktime(self.gmtime())
localAbsTime = AbsTime.AbsTime(localTime)
delta = localTime - gmTime
todayMidnight = AbsTime.absTimeYMD(localAbsTime.year, localAbsTime.month,
localAbsTime.day)
start = todayMidnight + (startHour * 3600) - delta
end = todayMidnight + (endHour * 3600) - delta
return TimeRange.TimeRange(start, end)
elif mode == "Database" and dbID.toJavaObj().getModelTime() != "00000000_0000":
start = dbID.modelTime() + (startHour * 3600)
end = dbID.modelTime() + (endHour * 3600)
return TimeRange.TimeRange(start, end)
else:
currentTime = self.gmtime()
today = AbsTime.absTimeYMD(currentTime.tm_year, currentTime.tm_mon,
currentTime.tm_mday)
start = today + (startHour * 3600)
end = today + (endHour * 3600)
return TimeRange.TimeRange(start, end)
def getSamplePoints(self, sampleSetName=None):
# Return a list of x,y tuples representing sample points
# sampleSet is the name of a saved sample set
# if sampleSet is None, the sample points will be
# those currently displayed on the GFE
points = []
sampleSet = self.__dataMgr.getSampleSetManager()
if sampleSetName is None:
locations = sampleSet.getLocations()
else:
locations = sampleSet.sampleSetLocations(sampleSetName)
for i in range(locations.size()):
xy = self.getGridLoc().gridCoordinate(locations.get(i))
points.append((xy.x, xy.y))
return points
def _timeDisplay(self, timeRange, LTorZulu, durFmt, startFmt, endFmt):
# Return a string display for the given timeRange, assumed to be
# in GMT.
# If LTorZulu == "LT", the timeRange will be converted from GMT
# to local time.
# durationFmt, startFmt, endFmt are format strings for the
# timeRange duration, the start time and end time respectively.
# See Text Product User Guide to see possible formats.
#
# Example:
# self._timeDisplay(timeRange, "LT", "%H hours ",
# "%a %b %d, %Y %I:%M %p",
# " to %a %b %d, %Y %I:%M %p %Z")
#
# yields a string such as:
#
# 12 hours Mon Apr 23, 2001 06:00 AM to Mon Apr 23, 2001 06:00 PM MDT.
if LTorZulu == "LT":
# Convert to local time
timeRange = self._shiftedTimeRange(timeRange)
display = ""
if durFmt != "":
duration = timeRange.duration()
durHours = duration / 3600
durMinutes = duration / 3600 / 60
durStr = string.replace(durFmt, "%H", `durHours`)
durStr = string.replace(durStr, "%M", `durMinutes`)
display = display + durStr
if startFmt != "":
display = display + timeRange.startTime().stringFmt(startFmt)
if endFmt != "":
display = display + timeRange.endTime().stringFmt(endFmt)
if LTorZulu == "LT":
# Adjust time zone to local time
localTime = self.localtime()
zoneName = time.strftime("%Z", localTime)
display = string.replace(display, "GMT", zoneName)
return display
def _shiftedTimeRange(self, timeRange):
localTime, shift = self._determineTimeShift()
return TimeRange.TimeRange(timeRange.startTime() + shift,
timeRange.endTime() + shift)
def _determineTimeShift(self):
''' Get the current Simulated UTC time and convert it to the
Site Time Zone as AbsTime return this and the number of seconds the
Simulated UTC time was shifted to get local time
'''
ldt = self._localtime()
shift = int(ldt.utcoffset().total_seconds())
currentTime = AbsTime.absTimeYMD(ldt.year, ldt.month, ldt.day, ldt.hour, ldt.minute)
return currentTime, shift
def _localtime(self, date=None, tz=None):
''' Assumes date (default is current Simulate Time) is a UTC time to convert
to the time zone tz (default is Site Time Zone).
returns datetime
'''
if tz is None:
tz = self.getTzInfo()
gmdt = self._gmtime(date)
tzdt = gmdt.astimezone(tz)
return tzdt
def _gmtime(self, date=None):
''' This takes date (default current Simulated Time) and converts it to AbsTime
'''
if date is None:
date = SimulatedTime.getSystemTime().getTime()
return AbsTime.AbsTime(date)
def gmtime(self, date=None):
''' This takes date (default current Simulated Time) and converts it to AbsTime
This should be used instead of time.gmtime()
'''
return self._gmtime(date).utctimetuple()
def localtime(self, date=None):
''' Assumes date (default is current Simulated Time) is a UTC time to convert
to the time zone of the local site.
This should be used instead of time.localtime()
'''
return self._localtime(date).timetuple()
def getTimeZoneStr(self):
''' Returns local time zone of the current site as a string
'''
return self.__gridLoc.getTimeZone()
def getTzInfo(self, tzname=None):
''' Returns time zone object compatible with datetime for the desired time zone.
Defaults to local site's time zone if tzname not specified.
'''
if tzname is None:
tzname = self.getTimeZoneStr()
import dateutil.tz
return dateutil.tz.gettz(tzname)
def dayTime(self, timeRange, startHour=6, endHour=18):
# Return 1 if start of timeRange is between the
# startHour and endHour, Return 0 otherwise.
# Assume timeRange is GMT and convert to local time.
shift = self.determineTimeShift()
startTime = timeRange.startTime() + shift
localStartHour = startTime.hour
if localStartHour >= startHour and localStartHour < endHour:
return 1
else:
return 0
def determineTimeShift(self):
loctime, shift = self._determineTimeShift()
return shift
def getEditArea(self, editAreaName):
# Returns an AFPS.ReferenceData object given an edit area name
# as defined in the GFE
# Example:
# myArea = self.getEditArea("BOU")
# self.callSmartTool("MyTool", "T", editArea=myArea, timeRange)
#
from com.raytheon.uf.common.dataplugin.gfe.reference import ReferenceID
refID = ReferenceID(editAreaName)
return self.__dataMgr.getRefManager().loadRefSet(refID)
def saveEditArea(self, editAreaName, refData):
# Saves the AFPS.ReferenceData object with the given name
from com.raytheon.uf.common.dataplugin.gfe.reference import ReferenceData, ReferenceID
refID = ReferenceID(editAreaName)
refData = ReferenceData(refData.getGloc(), refID, refData.getGrid())
self.__dataMgr.getRefManager().saveRefSet(refData)
def setActiveEditArea(self, area):
# Set the AFPS.ReferenceData area to be the active one in the GFE
# Note: This will not take effect until AFTER the smart tool or
# procedure is finished executing.
self.__dataMgr.getRefManager().setActiveRefSet(area)
def getActiveEditArea(self):
# Get the AFPS.ReferenceData area for the active one in the GFE
return self.__dataMgr.getRefManager().getActiveRefSet()
def clearActiveEditArea(self):
# Clear the active edit area in the GFE
#area = AFPS.ReferenceData_default()
#self.__dataMgr.referenceSetMgr().setActiveRefSet(area)
self.__dataMgr.getRefManager().clearRefSet()
def setActiveElement(self, model, element, level, timeRange,
colorTable=None, minMax=None, fitToData=0):
# Set the given element to the active one in the GFE
# A colorTable name may be given.
# A min/max range for the colorTable may be given.
# If fitToData = 1, the color table is fit to the data
#
# Example:
# self.setActiveElement("ISCDisc", WEname+"Disc", "SFC", GridTimeRange,
# colorTable="Discrepancy", minMax=(-20,+20),
# fitToData=1)
#
parm = self.getParm(model, element, level)
spatialMgr = self.__dataMgr.getSpatialDisplayManager()
if minMax or colorTable:
rsc = spatialMgr.getResourcePair(parm).getResource()
from com.raytheon.uf.viz.core.rsc.capabilities import ColorMapCapability
params = rsc.getCapability(ColorMapCapability).getColorMapParameters()
if colorTable:
from com.raytheon.uf.viz.core.drawables import ColorMapLoader
colorMap = ColorMapLoader.loadColorMap(colorTable)
elemType = parm.getGridInfo().getGridType().toString()
if ('DISCRETE' == elemType):
from com.raytheon.viz.gfe.rsc import DiscreteDisplayUtil
DiscreteDisplayUtil.deleteParmColorMap(parm)
params.setColorMap(colorMap)
params.setColorMapName(colorTable)
rsc.issueRefresh()
if minMax:
minVal, maxVal = minMax
if (minVal != maxVal):
params.setColorMapMax(maxVal)
params.setColorMapMin(minVal)
parm.getListeners().fireColorTableModified(parm)
if fitToData:
from com.raytheon.viz.gfe.rsc.colorbar import FitToData
fitter = FitToData(self.__dataMgr, parm)
fitter.fitToData()
spatialMgr.activateParm(parm)
spatialMgr.makeVisible(parm, True, True)
spatialMgr.setSpatialEditorTime(timeRange.startTime().javaDate())
def getActiveElement(self):
return self.__dataMgr.getSpatialDisplayManager().getActivatedParm()
def getGridCellSwath(self, editArea, cells):
from com.raytheon.uf.common.dataplugin.gfe.reference import ReferenceData
CoordinateType = ReferenceData.CoordinateType
# Returns an AFPS.ReferenceData swath of the given
# number of cells around the given an edit area.
# The edit area must not be a query.
if type(editArea) is types.StringType:
editArea = self.getEditArea(editArea)
grid2DB = None
multipolygon = editArea.getPolygons(CoordinateType.valueOf("GRID"))
numPolygons = multipolygon.getNumGeometries()
for n in range(numPolygons):
polygon = multipolygon.getGeometryN(n)
grid2DBit = self.getGridLoc().gridCellSwath(
polygon.getCoordinates(), float(cells), False)
if grid2DB is not None:
grid2DB = grid2DB.orEquals(grid2DBit)
else:
grid2DB = grid2DBit
return self.getGridLoc().convertToReferenceData(grid2DB)
def getLatLon(self, x, y):
# Get the latitude/longitude values for the given grid point
from com.vividsolutions.jts.geom import Coordinate
coords = Coordinate(float(x), float(y))
cc2D = self.getGridLoc().latLonCenter(coords)
return cc2D.y, cc2D.x
def getLatLonGrids(self):
gridLoc = self.getGridLoc()
latLonGrid = gridLoc.getLatLonGrid()
latLonGrid = numpy.reshape(latLonGrid, (2,gridLoc.getNy().intValue(),gridLoc.getNx().intValue()), order='F')
return latLonGrid[1], latLonGrid[0]
def getGridCell(self, lat, lon):
# Get the corresponding x,y values for the given lat/lon
# Return None, None if the lat/lon is outside the grid domain
cc2D = self.getGridLoc().gridCell(lat, lon)
gridSize = self.getGridLoc().gridSize()
if cc2D.x < 0 or cc2D.x >= gridSize.x or \
cc2D.y < 0 or cc2D.y >= gridSize.y:
return None, None
else:
return cc2D.x, cc2D.y
def getGrid2DBit(self, editArea):
# Returns a Grid of on/off values indicating whether
# or not the grid point is in the given edit area.
# This could be used as follows in a Smart Tool:
# def preProcessGrid(self):
# editArea = self.getEditArea("Area1")
# self.__area1Bits = self.getGrid2DBit(editArea)
# editArea = self.getEditArea("Area2")
# self.__area2Bits = self.getGrid2DBit(editArea)
#
# def execute(self, x, y):
# if self.__area1Bits.get(x,y) == 1:
# <process a point in Area1>
# elif self.__area2Bits.get(x,y) == 1:
# <process a point in Area2>
#
return editArea.getGrid()
def getGridTimes(self, model, element, level, startTime, hours):
# Return the timeRange and gridTimes for the number of hours
# FOLLOWING the given startTime
timeRange = TimeRange.TimeRange(startTime, startTime + hours * 3600)
parm = self.getParm(model, element, level, timeRange)
gridTimes = parm.getGridInfo().getTimeConstraints().constraintTimes(timeRange.toJavaObj())
pyList = []
for t in gridTimes:
pyList.append(TimeRange.TimeRange(t))
return timeRange, pyList
def getExprName(self, model, element, level="SFC", mostRecent=0):
# Return an expressionName for the element
# This method is complicated because it is handling all the
# variations for the "model" argument. For a description
# of the variations, see the "getValue" documentation above.
siteID = self.__mutableID.siteID()
if type(model) is types.StringType:
modelStr = model
else:
# Must be a databaseID, so get model string
modelStr = model.modelName()
if element == "Topo" or modelStr == self.__mutableID.modelName():
exprName = element
elif modelStr == "Official":
dbType = self.__mutableID.type()
modelName = "Official"
exprName = element + "_" + level + "_" + siteID + "_" + dbType + "_" + modelName
else:
if type(model) is types.StringType:
if string.count(model, "_") == 5:
# String as databaseID
dbID = DatabaseID.databaseID(model)
elif string.find(model, "_") < 0:
# Assume "on-the-fly" so need to prepend site
exprName = element + "_" + level + "_" + siteID + "__" + model
dbID = DatabaseID.databaseID_default()
else:
# Assume model is site_type_modelName
exprName = element + "_" + level + "_" + model
dbID = DatabaseID.databaseID_default()
else:
# Assume it is already a dbID
dbID = model
if dbID.siteID() is not None and dbID.siteID() != "":
if str(dbID) == str(self.__mutableID):
exprName = element
else:
exprName = element + "_" + level + "_" + dbID.siteID() + "_" + \
dbID.type() + "_" + dbID.modelName()
if mostRecent == 0:
if dbID.toJavaObj().getModelDate() is None:
exprName = exprName + "_00000000_0000"
else:
exprName = exprName + "_" + dbID.modelTime().stringFmt(
"%b%d%H")
return exprName
def getSiteID(self):
return self.__dataMgr.getSiteID()
def getModelName(self, databaseString):
# Return the model name.
# databaseString is the result of a VariableList entry of type
# "model" or "D2D_model"
dbID = DatabaseID.databaseID(databaseString)
return dbID.modelName()
def getD2Dmodel(self, model):
# Given a GFE Surface model, return the corresponding D2D model
if isinstance(model, DatabaseID.DatabaseID):
model = model.modelIdentifier()
d2d = string.replace(model, "__", "_D2D_")
return d2d
def getParm(self, model, element, level, timeRange=None, mostRecent=0):
# Returns the parm object for the given model, element, and level
exprName = self.getExprName(model, element, level, mostRecent)
#print "Expression Name", exprName
parm = self.__parmMgr.getParmInExpr(exprName, 1)
return parm
def getParmByExpr(self, exprName):
#print "Expression Name", exprName
parm = self.__parmMgr.getParmInExpr(exprName, 1)
return parm
##
# @param elementNames: ignored
#
# @deprecated: Cacheing is controlled by the system.
def cacheElements(self, elementNames):
pass
##
# Cacheing is controlled by the system. Users may still call this method
# to delete temporary parms in the parm manager.
#
# @param elementNames: ignored
def unCacheElements(self, elementNames):
self.__parmMgr.deleteTemporaryParms()
def loadWEGroup(self, groupName):
parmArray = self.__parmMgr.getAllAvailableParms();
parmIDs = self.__dataMgr.getWEGroupManager().getParmIDs(
groupName, parmArray)
# Load the group
self.__parmMgr.setDisplayedParms(parmIDs)
##
# @param model: Database model name
# @type model: String
# @param element: Element name, i.e., "Hazards"
# @type element: String
# @param level: Parm level, i.e., "SFC"
# @type level: String
# @return: None
def unloadWE(self, model, element, level, mostRecent=0):
# unloads the WE from the GFE
exprName = self.getExprName(model, element, level, mostRecent)
parm = self.__parmMgr.getParmInExpr(exprName, 1)
if parm is None:
return
parmJA = jep.jarray(1, parm)
parmJA[0] = parm
self.__parmMgr.deleteParm(parmJA)
def unloadWEs(self, model, elementLevelPairs, mostRecent=0):
jparms = []
for element, level in elementLevelPairs:
exprName = self.getExprName(model, element, level, mostRecent)
parm = self.__parmMgr.getParmInExpr(exprName, 1)
if parm:
jparms.append(parm)
if jparms:
parmJA = jep.jarray(len(jparms), jparms[0])
for i in xrange(len(jparms)):
parmJA[i] = jparms[i]
self.__parmMgr.deleteParm(parmJA)
def saveElements(self, elementList):
# Save the given Fcst elements to the server
# Example:
# self.saveElements(["T","Td"])
for element in elementList:
parm = self.getParm(self.mutableID(), element, "SFC")
parm.saveParameter(True)
def publishElements(self, elementList, timeRange):
# Publish the given Fcst elements to the server
# over the given time range.
# NOTE: This method is design to run from a procedure
# NOT a SmartTool!!!
# Example:
# self.publishElements(["T","Td"], timeRange)
from com.raytheon.uf.common.dataplugin.gfe.server.request import CommitGridRequest
requests = ArrayList()
for element in elementList:
# get the inventory for this element from the server
parm = self.getParm("Fcst", element, "SFC")
recList = self.__dataMgr.getClient().getPythonClient().getGridInventory(parm.getParmID())
publishTimeRange = timeRange
if recList is not None:
recSize = recList.size()
for x in range(recSize):
tr = TimeRange.TimeRange(recList.get(x))
if tr.overlaps(timeRange):
publishTimeRange = publishTimeRange.combineWith(tr)
cgr = CommitGridRequest(parm.getParmID(), publishTimeRange.toJavaObj())
requests.add(cgr)
self.__parmOp.publish(requests)
def combineMode(self):
from com.raytheon.viz.gfe.core.parm import ParmState
CombineMode = ParmState.CombineMode
mode = ParmState.getCurrentCombineMode()
if mode.equals(CombineMode.valueOf("COMBINE")):
return True
else:
return False
def setCombineMode(self, mode):
from com.raytheon.viz.gfe.core.parm import ParmState
CombineMode = ParmState.CombineMode
if mode == "Combine":
self.__parmOp.setCombineMode(CombineMode.valueOf("COMBINE"))
elif mode == "Replace":
self.__parmOp.setCombineMode(CombineMode.valueOf("REPLACE"))
else:
self.statusBarMsg("Invalid Weather Combine mode.", "S")
return None
def getVectorEditMode(self):
# Returns Vector Edit mode in the GFE
# mode:
# "Magnitude Only"
# "Direction Only"
# "Both"
from com.raytheon.viz.gfe.core.parm import ParmState
VectorMode = ParmState.VectorMode
mode = ParmState.getCurrentVectorMode()
if mode.equals(VectorMode.valueOf("MAGNITUDE")):
return "Magnitude Only"
elif mode.equals(VectorMode.valueOf("DIRECTION")):
return "Direction Only"
elif mode.equals(VectorMode.valueOf("BOTH")):
return "Both"
return "None"
def setVectorEditMode(self, mode):
# Sets the Vector Edit mode in the GFE
# mode:
# "Magnitude only"
# "Direction only"
# "Both"
from com.raytheon.viz.gfe.core.parm import ParmState
VectorMode = ParmState.VectorMode
if mode == "Magnitude Only":
self.__parmOp.setVectorMode(VectorMode.valueOf("MAGNITUDE"))
elif mode == "Direction Only":
self.__parmOp.setVectorMode(VectorMode.valueOf("DIRECTION"))
else:
self.__parmOp.setVectorMode(VectorMode.valueOf("BOTH"))
def getConfigItem(self, itemName, default=None):
# Return the configuration file value for "itemName"
# If not found, return the default given
from com.raytheon.viz.gfe import Activator
prefs = Activator.getDefault().getPreferenceStore()
if prefs.contains(itemName):
if prefs.isString(itemName):
return str(prefs.getString(itemName))
elif prefs.isInt(itemName):
return prefs.getInt(itemName)
elif prefs.isFloat(itemName):
return prefs.getFloat(itemName)
elif prefs.isDouble(itemName):
return prefs.getDouble(itemName)
elif prefs.isLong(itemName):
return prefs.getLong(itemName)
elif prefs.isBoolean(itemName):
return prefs.getBoolean(itemName)
elif prefs.isStringArray(itemName):
pa = []
jsa = prefs.getStringArray(itemName)
for i in jsa:
pa.append(str(i))
return pa
elif prefs.isFloatArray(itemName):
pa = []
jsa = prefs.getFloatArray(itemName)
for i in jsa:
pa.append(i.floatValue())
return pa
elif prefs.isIntArray(itemName):
pa = []
jsa = prefs.getIntArray(itemName)
for i in jsa:
pa.append(i.intValue())
return pa
else:
return default
else:
return default
def esat(self, temp):
return exp(26.660820 - 0.0091379024 * temp - 6106.3960 / temp)
##
# Get the discrete keys for elementName.
#
# @param elementName: Name of an element.
# @type elementName: string
# @return: the keys for the element
# @rtype: list of strings
def getDiscreteKeys(self, elementName):
parm = self.getParm("Fcst", elementName, "SFC")
keyList = parm.getGridInfo().getDiscreteKeys()
keyList = JUtil.javaStringListToPylist(keyList)
return keyList
#########################################################################
## Numeric Python methods ##
#########################################################################
def getTopo(self):
# Return the numeric topo grid
if self.__topoGrid is None:
topo = self.__parmMgr.getParmInExpr("Topo", True)
self.__topoGrid = self.__cycler.getCorrespondingResult(
topo, TimeRange.allTimes().toJavaObj(), "TimeWtAverage")[0]
self.__topoGrid = self.__topoGrid.getGridSlice().getNDArray()
return self.__topoGrid
def wxMask(self, wx, query, isreg=0):
# Returns a numeric mask i.e. a grid of 0's and 1's
# where the value is 1 if the given query succeeds
# Arguments:
# wx -- a 2-tuple:
# wxValues : numerical grid of byte values
# keys : list of "ugly strings" where the index of
# the ugly string corresponds to the byte value in
# the wxValues grid.
# query -- a text string representing a query
# isreg -- if 1, the query is treated as a regular expression
# otherwise as a literal string
# Examples:
# # Here we want to treat the query as a regular expression
# PoP = where(self.wxMask(wxTuple, "^Chc:", 1), maximum(40, PoP), PoP)
# # Here we want to treat the query as a literal
# PoP = where(self.wxMask(wxTuple, ":L:") maximum(5, PoP), PoP)
#
rv = self.empty(bool)
if not isreg:
for i in xrange(len(wx[1])):
#if fnmatch.fnmatchcase(wx[1][i], query):
if string.find(wx[1][i], query) >= 0:
rv[equal(wx[0], i)] = True
else:
r = re.compile(query)
for i in xrange(len(wx[1])):
m = r.search(wx[1][i])
if m is not None:
rv[equal(wx[0], i)] = True
return rv
# Returns a numeric mask i.e. a grid of 0's and 1's
# where the value is 1 if the given query succeeds
# Arguments:
# Discrete -- a 2-tuple:
# grid : numerical grid of byte values
# keys : list of "ugly strings" where the index of
# the ugly string corresponds to the byte value in
# the wxValues grid.
# query -- a text string representing a query
# isreg -- if 1, the query is treated as a regular expression
# otherwise as a literal string
# Examples:
# # Here we want to treat the query as a regular expression
# PoP = where(self.wxMask(wxTuple, "^Chc:", 1), maximum(40, PoP), PoP)
# # Here we want to treat the query as a literal
# PoP = where(self.wxMask(wxTuple, ":L:") maximum(5, PoP), PoP)
discreteMask = wxMask
##
# Sort the subkeys of uglyStr alphabetically.
#
# @param uglyStr: A key with "^"s separating subkeys
# @type uglyStr: string
# @return: uglyStr with alpha sorted subkeys.
# @rtype: string
def sortUglyStr(self, uglyStr):
parts = uglyStr.split("^")
if len(parts) < 2:
return uglyStr
# do the sort
parts.sort()
sortedStr = "^".join(parts)
return sortedStr
##
# Get the index of uglyStr within keys.
# This routine compares normalized (sorted) versions of uglyStr and
# keys to be sure that equivalent hazards are assigned the same grid
# index.
# When a matching key is not in keys, uglyStr will be added to keys
# and the index of the new entry will be returned.
#
# @param uglyStr: A hazard key
# @type uglyStr: string
# @param keys: Existing hazard keys
# @type keys: list
# @return: The index of a key equivalent to uglyStr in keys
# @rtype: int
def getIndex(self, uglyStr, keys):
# Returns the byte value that corresponds to the
# given ugly string. It will add a new key if a new ugly
# string is requested.
# Arguments:
# uglyStr: a string representing a weather value
# keys: a list of ugly strings.
# A Wx argument represents a 2-tuple:
# wxValues : numerical grid of byte values
# keys : list of "ugly strings" where the index of
# the ugly string corresponds to the byte value in the wxValues grid.
# For example, if our keys are:
# "Sct:RW:-:<NoVis>:"
# "Chc:T:-:<NoVis>:"
# "Chc:SW:-:<NoVis>:"
# Then, the wxValues grid will have byte values of 0 where
# there is "Sct:RW:-:<NoVis>:", 1 where there is "Chc:T:-:<NoVis>:"
# and 2 where there is "Chc:SW:-:<NoVis>:"
#
# The ugly strings are also used by DISCRETE. The keys are
# separated by '^' for the subkeys.
# 18 Nov 2005 - tl
# Added sorting to ugly strings to prevent duplicate keys
# Duplicate keys causes a bug when generating hazards grids.
sortedUglyStr = self.sortUglyStr(uglyStr)
for str in keys:
if sortedUglyStr == self.sortUglyStr(str):
return keys.index(str)
if len(keys) >= 256:
raise IndexError("Attempt to create more than 256 Wx keys")
keys.append(uglyStr)
return len(keys) - 1
##
# Returns a Numeric Python mask for the edit area
# "editArea" can be a named area or a ReferenceData object
# @param editArea: An edit area to obtain a mask for
# @type editArea: String or referenceArea
# @return: grid for the edit area
# @rtype: numpy array of int8
def encodeEditArea(self, editArea):
# Returns a Numeric Python mask for the edit area
# "editArea" can be a named area or a referenceData object
if type(editArea) is types.StringType:
editArea = self.getEditArea(editArea)
if editArea.isQuery():
editArea = self.__refSetMgr.evaluateQuery(editArea.getQuery())
return editArea.getGrid().getNDArray().astype(bool)
def decodeEditArea(self, mask):
# Returns a refData object for the given mask
from com.raytheon.uf.common.dataplugin.gfe.grid import Grid2DBit
from com.raytheon.uf.common.dataplugin.gfe.reference import ReferenceData, ReferenceID
gridLoc = self.getGridLoc()
nx = gridLoc.getNx().intValue()
ny = gridLoc.getNy().intValue()
# force mask to boolean if it's not
mask = NumpyJavaEnforcer.checkdTypes(mask, bool)
# convert boolean mask to bytes for Grid2DBit
bytes = mask.astype(int8)
grid = Grid2DBit.createBitGrid(nx, ny, bytes)
return ReferenceData(gridLoc, ReferenceID("test"), grid)
def getindicies(self, o, l):
if o > 0:
a = slice(o, l); b = slice(0, l - o)
elif o < 0:
a = slice(0, l + o); b = slice(- o, l)
else:
a = slice(0, l); b = slice(0, l)
return a, b
def offset(self, a, x, y):
# Gives an offset grid for array, a, by x and y points
sy1, sy2 = self.getindicies(y, a.shape[0])
sx1, sx2 = self.getindicies(x, a.shape[1])
b = zeros_like(a)
b[sy1, sx1] = a[sy2, sx2]
return b
def agradient(self, a):
# Gives offset grids in the "forward" x and "up" y directions
dx = a - self.offset(a, 1, 0)
dy = a - self.offset(a, 0, - 1)
return dx, dy
def diff2(self, x, n=1, axis= - 1):
"""diff2(x,n=1,axis=-1) calculates the first-order, discrete
center difference approximation to the derivative along the axis
specified. array edges are padded with adjacent values.
"""
a = asarray(x)
nd = len(a.shape)
slice1 = [slice(None)] * nd
slice2 = [slice(None)] * nd
slice1[axis] = slice(2, None)
slice2[axis] = slice(None, - 2)
tmp = a[slice1] - a[slice2]
rval = zeros_like(a)
slice3 = [slice(None)] * nd
slice3[axis] = slice(1, - 1)
rval[slice3] = tmp
slice4 = [slice(None)] * nd
slice4[axis] = slice(0, 1)
rval[slice4] = tmp[slice4]
slice5 = [slice(None)] * nd
slice5[axis] = slice(- 1, None)
rval[slice5] = tmp[slice5]
if n > 1:
return diff2(rval, n - 1)
else:
return rval
##
# Get the grid shape from the GridLocation stored in the parm manager.
#
# @return: The number of data points in the X and Y directions.
# @rtype: 2-tuple of int
def getGridShape(self):
return self.__gridShape
#########################################################################
## Procedure methods ##
#########################################################################
# These commands always apply to the mutable model only.
# NOTE: Most of these commands are duplicated with "old" and
# "recommended" versions which end in "Cmd". For example, "copy"
# is the "old" version and will eventually not be supported
# while the recommended version is "copyCmd".
# Command Arguments:
# name1, name2, name3 is a list of the weather element names
# startHour is the starting hour for the command offset from modelbase
# endHour is the ending hour for the command offset from modelbase.
# The ending hour is NOT included in the processing of the
# command.
# modelbase is the name of the model to be used to determine base times
# Note that if this is "", then 0000z from today will be
# used for the base time.
# modelsource is the name of the model to be used in the copy command
# copyOnly is 0 for move and 1 for copy only in the time shift command
# hoursToShift is the number of hours to shift the data in time
# shift command
# databaseID must be of type AFPS.DatabaseID
# Can be obtained in various ways:
# --By calling findDatabase (see below)
# --By calling getDatabase (see below) with the result
# of a VariableList entry of type "model" or "D2D_model"
# timeRange must be of type AFPS.TimeRange.
# Can be obtained in various ways:
# --As an argument passed into Smart Tool or Procedure,
# --By calling getTimeRange (see below)
# --By calling createTimeRange (see below)
# List of available commands:
# copyCmd(['name1', 'name2', 'name3'], databaseID, timeRange)
# Copies all grids for each weather element from the given database
# into the weather element in the mutable database that overlaps
# the time range.
# Example:
# databaseID = self.findDatabase("NAM12") # Most recent NAM12 model
# timeRange = self.createTimeRange(0, 49, "Database", databaseID)
# self.copyCmd(['T', 'Wind'], databaseID, timeRange)
# will copy the Temperature and Wind fields analysis through 48 hours
# from the latest NAM12 and place them into the forecast.
# copyToCmd([('srcName1', 'dstName1'),
# ('srcName2', 'dstName2')], databaseID, timeRange)
# Copies all grids for each weather element from the given database
# into the weather element in the mutable database that overlaps
# the time range. The source name and destination name are both
# supplied. This allows for copying data with different names
# (The units must match).
# Example:
# databaseID = self.findDatabase("NAM12") # Most recent NAM12 model
# timeRange = self.createTimeRange(0, 49, "Database", databaseID)
# self.copyToCmd([('MaxT', 'T'), ('T', 'MinT')], databaseID,
# timeRange)
# will copy the Max Temperature into T and T into MinT.
# from the latest NAM12 and place them into the forecast.
#
# deleteCmd(['name1', 'name2', 'name3'], timeRange)
# Deletes all grids that overlap the input time range for element
# in the mutable database.
# Example:
# databaseID = self.findDatabase("NAM12") # Most recent NAM12 model
# timeRange = self.createTimeRange(0, 49, "Database", databaseID)
# self.deleteCmd(['T', 'Wind'], timeRange)
# will delete the Temperature and Wind fields analysis up to
# but not including 48 hours relative to the start time of
# the latest NAM12 model.
#
# zeroCmd(['name1', 'name2', 'name3'], timeRange)
# Assigns the minimum possible value for scalar and vector, and "<NoWx>"
# for weather for the parameter in the mutable database for all grids
# that overlap the specified time range.
# Example:
# databaseID = self.findDatabase("NAM12") # Most recent NAM12 model
# timeRange = self.createTimeRange(0, 49, "Database", databaseID)
# self.zeroCmd(['T', 'Wind'], databaseID, timeRange)
# will zero the Temperature and Wind grids through 48 hours
# relative to the start time of the latest NAM12 model.
#
# interpolateCmd(['name1', 'name2', 'name3'], timeRange,
# interpMode="GAPS", interpState="SYNC", interval=0, duration=0)
# Interpolates data in the forecast for the named weather elements
# for the given timeRange.
# Example:
# databaseID = self.findDatabase("NAM12") # Most recent NAM12 model
# timeRange = self.createTimeRange(0, 49, "Database", databaseID)
# self.interpolateCmd(['T', 'Wind'], timeRange, "GAPS","SYNC")
# will interpolate the Temperature and Wind grids up to but
# but not including 48 hours relative to the start time of
#the latest NAM12 model.
# The interpolation will run in SYNC mode i.e. completing before
# continuing with the procedure.
#
# createFromScratchCmd(['name1', 'name2'], timeRange, repeat, duration)
# Creates one or more grids from scratch over the given timeRange
# and assigns the default (minimum possible value for scalar
# and vector, "<NoWx>" for weather).
# The repeat interval and duration (both specified in hours) are
# used to control the number of grids created. If 0 is specified for
# either one, than only 1 grid is created for the given time range. If
# valid numbers for duration and repeat are given, then grids will
# be created every "repeat" hours and they will have a duration
# of "duration" hours. If there is not enough room remaining to create
# a grid with the full duration, then no grid will be created in the space
# remaining. If you don't get the desired results, be sure that your input
# time range starts on a valid time constraint for the element. If the
# element's time constraints (not the values supplied in this routine) contains
# gaps (i.e., duration != repeatInterval), then the repeat interval and
# duration will be ignored and grids will be created for each possible
# constraint time.
# Example:
# databaseID = self.findDatabase("NAM12") # Most recent NAM12 model
# timeRange = self.createTimeRange(0, 49, "Database", databaseID)
# self.createFromScratchCmd(['T', 'Wind'], timeRange, 3, 1)
# will create the 1-hour Temperature grids through 48 hours at
# 3 hour intervals relative to the start time of the latest NAM12 model.
#
# timeShiftCmd(['name1', 'name2'], copyOnly, shiftAmount, timeRange)
# Performs a time shift by the shiftAmount for all elements that
# overlap the time range.
# Example:
# databaseID = self.findDatabase("NAM12") # Most recent NAM12 model
# timeRange = self.createTimeRange(0, 49, "Database", databaseID)
# self.timeShiftCmd(['T', 'Wind'], 1, 3, timeRange)
#
# splitCmd(elements, timeRange)
# Splits any grid that falls on the start time or ending time of the
# specified time range for the given parameter in the mutable database.
#
# fragmentCmd(elements, timeRange)
# Fragments any grids that overlap the input time range for the parm
# identified in the mutable database.
#
# assignValueCmd(elements, timeRange, value)
# Assigns the specified value to all grids points for the grids that
# overlap the specified time range, for the weather element in the mutable
# database specified.
# value is:
# an Integer or Float for SCALAR
# a magnitude-direction tuple for VECTOR: e.g. (55,120)
# a text string for Weather which can be obtained via the
# WxMethods WxString method
# Example:
# # Scalar
# value = 60
# self.assignValue(["T","Td"], 0, 12, 'NAM12', value)
# # Vector
# value = (15, 120)
# self.assignValue(["Wind"], 0, 12, 'NAM12', value)
# # Weather
# from WxMethods import *
# value = WxString("Sct RW")
# self.assignValue(["Wx"], 0, 12, 'NAM12', value)
# Example: Copy RAP40 0-12, NAM12 13-48, GFS80 49-72 for T, Wx, and Wind,
# and then interpolate from hours 0 - 24.
#
#
# self.copy(['T','Wx', 'Wind'], 0, 12, 'RAP40')
# self.copy(['T','Wx', 'Wind'], 13, 48, 'NAM12')
# self.copy(['T','Wx', 'Wind'], 49, 72, 'GFS80')
# self.interpolate(['T','Wx', 'Wind'], 0, 24, 'RAP40')
def copyCmd(self, elements, databaseID, timeRange):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
if isinstance(databaseID, DatabaseID.DatabaseID):
databaseID = databaseID.toJavaObj()
for element in elements:
self.__parmOp.copyCmd(element, databaseID, timeRange)
def copyToCmd(self, elements, databaseID, timeRange):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
if isinstance(databaseID, DatabaseID.DatabaseID):
databaseID = databaseID.toJavaObj()
for src, dst in elements:
self.__parmOp.copyToCmd(src, dst, databaseID, timeRange)
def deleteCmd(self, elements, timeRange):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
for element in elements:
self.__parmOp.deleteCmd(element, timeRange)
def zeroCmd(self, elements, timeRange):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
for element in elements:
self.__parmOp.zeroCmd(element, timeRange)
def interpolateCmd(self, elements, timeRange,
interpMode="GAPS", interpState="SYNC", interval=0,
duration=0):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
# Convert interval to seconds
interval = interval * 3600
for element in elements:
self.__parmOp.interpolateCmd(element, timeRange,
interpMode, interpState, interval,
duration)
def createFromScratchCmd(self, elements, timeRange, repeat=0, duration=0):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
# Convert repeat and duration to seconds
repeat = repeat * 3600
duration = duration * 3600
for element in elements:
self.__parmOp.createFromScratchCmd(element, timeRange, repeat, duration)
def timeShiftCmd(self, elements, copyOnly, shiftAmount, timeRange):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
shiftAmount = shiftAmount * 3600
for element in elements:
self.__parmOp.timeShiftCmd(element, timeRange, copyOnly,
shiftAmount)
def splitCmd(self, elements, timeRange):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
for element in elements:
self.__parmOp.splitCmd(element, timeRange)
def fragmentCmd(self, elements, timeRange):
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
for element in elements:
self.__parmOp.fragmentCmd(element, timeRange)
def assignValueCmd(self, elements, timeRange, value):
from com.raytheon.viz.gfe.core.wxvalue import ScalarWxValue, VectorWxValue, WeatherWxValue
if isinstance(timeRange, TimeRange.TimeRange):
timeRange = timeRange.toJavaObj()
for element in elements:
parm = self.__parmMgr.getParmInExpr(element, 1)
if type(value) == types.TupleType:
newvalue = VectorWxValue(float(value[0]), float(value[1]), parm)
elif type(value) == types.StringType:
newvalue = WeatherKey(value)
newvalue = WeatherWxValue(newvalue, parm)
else:
newvalue = ScalarWxValue(float(value), parm)
self.__parmOp.assignValueCmd(element, timeRange, newvalue)
def __getUserFile(self, name, category):
from com.raytheon.uf.common.localization import PathManagerFactory, LocalizationContext
LocalizationType = LocalizationContext.LocalizationType
LocalizationLevel = LocalizationContext.LocalizationLevel
pathMgr = PathManagerFactory.getPathManager()
path = 'gfe/userPython/' + category + '/' + name
lc = pathMgr.getContext(LocalizationType.valueOf('CAVE_STATIC'), LocalizationLevel.valueOf('USER'))
lf = pathMgr.getLocalizationFile(lc, path)
return lf
def saveObject(self, name, object, category):
import cPickle
# Save a Python object (e.g. a Numeric grid)
# in the server under the given name
# Example:
# self.saveObject("MyGrid", numericGrid, "DiscrepancyValueGrids")
#
lf = self.__getUserFile(name, category)
fullpath = lf.getFile().getPath()
idx = fullpath.rfind("/")
if not os.path.exists(fullpath[:idx]):
os.makedirs(fullpath[:idx])
openfile = open(fullpath, 'w')
cPickle.dump(object, openfile)
openfile.close()
lf.save()
def getObject(self, name, category):
import cPickle
# Returns the given object stored in the server
# Example:
# discrepancyValueGrid = self.getObject("MyGrid","DiscrepancyValueGrids")
#
lf = self.__getUserFile(name, category)
fullpath = lf.getFile().getPath()
openfile = open(fullpath, 'r')
obj = cPickle.load(openfile)
openfile.close()
return obj
def deleteObject(self, name, category):
# Delete the given object stored in the server
# Example:
# self.deleteObject("MyGrid", "DiscrepancyValueGrids")
#
lf = self.__getUserFile(name, category)
lf.delete()
def myOfficeType(self):
#returns my configured office type, such as "wfo" or "rfc"
return self.__dataMgr.getOfficeType()
def officeType(self, siteid):
#returns the office type for the given site identifier
#returns None if unknown site id
a = self.__dataMgr.officeType(siteid)
if len(a):
return a
else:
return None
def availableDatabases(self):
dbs = []
availDbs = self.__parmMgr.getAvailableDbs()
for i in range(availDbs.size()):
dbId = availDbs.get(i)
dbs.append(DatabaseID.DatabaseID(dbId))
return dbs
def knownOfficeTypes(self):
import JUtil
return JUtil.javaStringListToPylist(self.__dataMgr.knownOfficeTypes())
# Retrieves a text product from the text database
def getTextProductFromDB(self, productID):
from com.raytheon.viz.gfe.product import TextDBUtil
opMode = self.gfeOperatingMode() in ("OPERATIONAL", "TEST")
fullText = TextDBUtil.retrieveProduct(productID, opMode)
textList = fullText.splitlines(True)
return textList
def callTextFormatter(self, productName, dbId, varDict={}, vtecMode=None):
"""
Execute the requested text product formatter.
Args:
productName: the display name of the formatter to run.
dbId: string form of the DatabaseID to use as data source.
varDict: optional, product varDict, use an empty dict instead
of None to signify a null varDict.
vtecMode: optional, for VTEC products specify VTEC mode (one of
'O', 'T', 'E' or 'X').
Returns:
The output of the formatter--the content of the requested product.
Throws:
TypeError: If varDict is not a dict.
RuntimeError: If the formatter fails during execution.
"""
if type(varDict) is not dict:
raise TypeError("Argument varDict must be a dict.")
varDict = str(varDict)
listener = TextProductFinishWaiter()
FormatterUtil.callFromSmartScript(productName, dbId, varDict, vtecMode, self.__dataMgr, listener)
product = listener.waitAndGetProduct()
state = listener.getState()
if not state.equals(ProductStateEnum.Finished):
msg = "Formatter " + productName + " terminated before completion with state: " + state.name() + \
". Check formatter logs from Process Monitor for more information."
raise RuntimeError(msg)
return product
def saveCombinationsFile(self, name, combinations):
"""
Save the specified zone combinations to the localization data store.
Args:
name: Name for the combinations file. The ".py" extension will
automatically be appended to the final file name.
combinations: The zone combinations. This data structure should
be a list of list of zone names
(e.g. [["OAX", "GID", "LBF"], ["DMX"], ["FSD", "ABR"]]
Throws:
TypeError: If combinations is not in the proper format.
"""
# Validate that we were passed a collection of collections, we'll convert
# to list of lists to satisfy the Java type checker.
try:
for item in iter(combinations):
iter(item)
except TypeError:
raise TypeError("combinations must be a list of list of zone names.")
combo_list = JUtil.pyValToJavaObj([[str(zone) for zone in group] for group in combinations])
CombinationsFileUtil.generateAutoCombinationsFile(combo_list, str(name))
def loadCombinationsFile(self, name):
"""
Load the specified zone combinations file form the localization data store.
Args:
name: Name for the combinations file. The ".py" extension will
automatically be appended to the final file name.
Returns:
The zone combinations as a list of lists of zone names
(e.g. [["OAX", "GID", "LBF"], ["DMX"], ["FSD", "ABR"]]
"""
return JUtil.javaObjToPyVal(CombinationsFileUtil.init(name))
def transmitTextProduct(self, product, wanPil, wmoType):
"""
Transmit the specified product. Will automatically detect if GFE is
operating in OPERATIONAL or PRACTICE mode and send using the appropriate
route.
Args:
product: the text or body of the product to transmit.
wanPil: the AWIPS WAN PIL for the product
wmoType: The WMO type of the product.
Returns:
The status of the transmission request as a ProductStateEnum.
"""
wanPil = str(wanPil)
product = str(product)
wmoType = str(wmoType)
transmitter = TextProductTransmitter(product, wanPil, wmoType)
practice = self.gfeOperatingMode()=="PRACTICE"
status = transmitter.transmitProduct(practice)
return status
def sendWFOMessage(self, wfos, message):
'''
Sends a message to a list of wfos
Args:
wfos: string or list, set or tuple of strings containing the destination wfo(s)
message: string containing the message to be sent
Returns:
string: empty if successful or error message
Raises:
TypeError: if wfos is not a string, list, tuple or set
'''
if not wfos:
# called with empty wfo list, nothing to do
return ""
javaWfos = ArrayList()
if type(wfos) in [list, tuple, set]:
for wfo in wfos:
javaWfos.add(wfo)
elif type(wfos) is str:
javaWfos.add(wfos)
else:
raise TypeError("Invalid type received for wfos: " + type(wfos))
response = self.__dataMgr.getClient().sendWFOMessage(javaWfos, message)
return response.message()