Omaha #4480: Implement API for retrieving pure python soundings through DAF.
Change-Id: I11801ce387b2822e3276c4bfa715bc475afbe506 Former-commit-id: f222bee8ed317033e97fcea8d0611d666653402a
This commit is contained in:
parent
1b3a96d2e6
commit
b59b5d3cd7
8 changed files with 657 additions and 61 deletions
|
@ -28,6 +28,8 @@
|
|||
# ------------ ---------- ----------- --------------------------
|
||||
# 05/29/13 2023 dgilling Initial Creation.
|
||||
# 02/12/14 2672 bsteffen Allow String constructor to parse floats.
|
||||
# 06/29/15 4480 dgilling Implement __hash__, __eq__,
|
||||
# __str__ and rich comparison operators.
|
||||
#
|
||||
|
||||
|
||||
|
@ -58,6 +60,120 @@ class Level(object):
|
|||
if levelTwo:
|
||||
self.leveltwovalue = numpy.float64(levelTwo)
|
||||
|
||||
def __hash__(self):
|
||||
# XOR-ing the 3 items in a tuple ensures that order of the
|
||||
# values matters
|
||||
hashCode = hash(self.masterLevel) ^ hash(self.levelonevalue) ^ hash(self.leveltwovalue)
|
||||
hashCode ^= hash((self.masterLevel, self.levelonevalue, self.leveltwovalue))
|
||||
return hashCode
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(self) != type(other):
|
||||
return False
|
||||
else:
|
||||
return (self.masterLevel, self.levelonevalue, self.leveltwovalue) == \
|
||||
(other.masterLevel, other.levelonevalue, other.leveltwovalue)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
elif self.masterLevel.getName() != other.masterLevel.getName():
|
||||
return NotImplemented
|
||||
|
||||
myLevel1 = self.levelonevalue
|
||||
myLevel2 = self.leveltwovalue
|
||||
otherLevel1 = other.levelonevalue
|
||||
otherLevel2 = other.leveltwovalue
|
||||
if myLevel1 == INVALID_VALUE and myLevel2 != INVALID_VALUE:
|
||||
myLevel1 = myLevel2
|
||||
myLevel2 = INVALID_VALUE
|
||||
if otherLevel1 == INVALID_VALUE and otherLevel2 != INVALID_VALUE:
|
||||
otherLevel1 = otherLevel2
|
||||
otherLevel2 = INVALID_VALUE
|
||||
|
||||
# We default to descending order to make sorting levels from the DAF easier
|
||||
compareType = self.masterLevel.getType() if self.masterLevel.getType() else "DEC"
|
||||
if myLevel1 != INVALID_VALUE and otherLevel1 != INVALID_VALUE:
|
||||
level1Cmp = self.__compareLevelValues(compareType, myLevel1, otherLevel1)
|
||||
if level1Cmp == -1:
|
||||
if myLevel2 != INVALID_VALUE and otherLevel2 != INVALID_VALUE:
|
||||
level2Cmp = self.__compareLevelValues(compareType, myLevel2, otherLevel2)
|
||||
return level2Cmp == -1
|
||||
elif myLevel2 != INVALID_VALUE:
|
||||
level2Cmp = self.__compareLevelValues(compareType, myLevel2, otherLevel1)
|
||||
return level2Cmp == -1
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __le__(self, other):
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
elif self.masterLevel.getName() != other.masterLevel.getName():
|
||||
return NotImplemented
|
||||
|
||||
return self.__lt__(other) or self.__eq__(other)
|
||||
|
||||
def __gt__(self, other):
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
elif self.masterLevel.getName() != other.masterLevel.getName():
|
||||
return NotImplemented
|
||||
|
||||
myLevel1 = self.levelonevalue
|
||||
myLevel2 = self.leveltwovalue
|
||||
otherLevel1 = other.levelonevalue
|
||||
otherLevel2 = other.leveltwovalue
|
||||
if myLevel1 == INVALID_VALUE and myLevel2 != INVALID_VALUE:
|
||||
myLevel1 = myLevel2
|
||||
myLevel2 = INVALID_VALUE
|
||||
if otherLevel1 == INVALID_VALUE and otherLevel2 != INVALID_VALUE:
|
||||
otherLevel1 = otherLevel2
|
||||
otherLevel2 = INVALID_VALUE
|
||||
|
||||
# We default to descending order to make sorting levels from the DAF easier
|
||||
compareType = self.masterLevel.getType() if self.masterLevel.getType() else "DEC"
|
||||
if myLevel1 != INVALID_VALUE and otherLevel1 != INVALID_VALUE:
|
||||
level1Cmp = self.__compareLevelValues(compareType, myLevel1, otherLevel1)
|
||||
if level1Cmp == 1:
|
||||
if myLevel2 != INVALID_VALUE and otherLevel2 != INVALID_VALUE:
|
||||
level2Cmp = self.__compareLevelValues(compareType, myLevel2, otherLevel2)
|
||||
return level2Cmp == 1
|
||||
elif otherLevel2 != INVALID_VALUE:
|
||||
level2Cmp = self.__compareLevelValues(compareType, myLevel1, otherLevel2)
|
||||
return level2Cmp == 1
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __ge__(self, other):
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
elif self.masterLevel.getName() != other.masterLevel.getName():
|
||||
return NotImplemented
|
||||
|
||||
return self.__gt__(other) or self.__eq__(other)
|
||||
|
||||
def __compareLevelValues(self, compareType, val1, val2):
|
||||
returnVal = 0
|
||||
if val1 < val2:
|
||||
returnVal = -1 if compareType == 'INC' else 1
|
||||
elif val2 < val1:
|
||||
returnVal = 1 if compareType == 'INC' else -1
|
||||
return returnVal
|
||||
|
||||
def __str__(self):
|
||||
retVal = ""
|
||||
if INVALID_VALUE != self.levelonevalue:
|
||||
retVal += str(self.levelonevalue)
|
||||
if INVALID_VALUE != self.leveltwovalue:
|
||||
retVal += "_" + str(self.leveltwovalue)
|
||||
retVal += str(self.masterLevel.getName())
|
||||
return retVal
|
||||
|
||||
def getId(self):
|
||||
return self.id
|
||||
|
||||
|
@ -87,4 +203,3 @@ class Level(object):
|
|||
|
||||
def setIdentifier(self, identifier):
|
||||
self.identifier = identifier
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
# Date Ticket# Engineer Description
|
||||
# ------------ ---------- ----------- --------------------------
|
||||
# 05/29/13 2023 dgilling Initial Creation.
|
||||
# 06/29/15 4480 dgilling Implement __hash__, __eq__
|
||||
# and __str__.
|
||||
#
|
||||
#
|
||||
|
||||
|
@ -39,6 +41,27 @@ class MasterLevel(object):
|
|||
self.type = None
|
||||
self.identifier = None
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(self) != type(other):
|
||||
return False
|
||||
else:
|
||||
return self.name == other.name
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __str__(self):
|
||||
retVal = "MasterLevel["
|
||||
retVal += "name=" + str(self.name) + ","
|
||||
retVal += "type=" + str(self.type) + ","
|
||||
retVal += "unit=" + str(self.unitString) + ","
|
||||
retVal += "description=" + str(self.description)
|
||||
retVal += "]"
|
||||
return retVal
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
# 01/22/14 2667 bclement preserved milliseconds in string representation
|
||||
# 03/03/14 2673 bsteffen allow construction using a Date for refTime
|
||||
# 06/24/14 3096 mnash implement __cmp__
|
||||
# 06/24/15 4480 dgilling implement __hash__ and __eq__,
|
||||
# replace __cmp__ with rich comparison
|
||||
# operators.
|
||||
#
|
||||
|
||||
import calendar
|
||||
|
@ -66,9 +69,7 @@ class DataTime(object):
|
|||
self.refTime = long(self.refTime.getTime())
|
||||
else:
|
||||
self.refTime = long(refTime)
|
||||
dateObj = Date()
|
||||
dateObj.setTime(self.refTime)
|
||||
self.refTime = dateObj
|
||||
self.refTime = Date(self.refTime)
|
||||
|
||||
if self.validPeriod is None:
|
||||
validTimeMillis = self.refTime.getTime() + long(self.fcstTime * 1000)
|
||||
|
@ -112,38 +113,6 @@ class DataTime(object):
|
|||
def setLevelValue(self, levelValue):
|
||||
self.levelValue = numpy.float64(levelValue)
|
||||
|
||||
def __cmp__(self, other):
|
||||
if other is None :
|
||||
return 1
|
||||
|
||||
# compare the valid times, which are the ref times + forecast times
|
||||
validTimeCmp = cmp(self.getRefTime().getTime() + self.getFcstTime(),
|
||||
other.getRefTime().getTime() + other.getFcstTime())
|
||||
if validTimeCmp != 0 :
|
||||
return validTimeCmp
|
||||
|
||||
# compare the forecast times
|
||||
fcstTimeCmp = cmp(self.getFcstTime(), other.getFcstTime())
|
||||
if fcstTimeCmp != 0 :
|
||||
return fcstTimeCmp
|
||||
|
||||
# compare the level values
|
||||
levelCmp = cmp(self.getLevelValue(), other.getLevelValue())
|
||||
if levelValue != 0 :
|
||||
return levelValue
|
||||
|
||||
# compare the valid periods
|
||||
period1 = self.getValidPeriod()
|
||||
period2 = other.getValidPerid()
|
||||
|
||||
if period1 is None :
|
||||
return -1
|
||||
elif period2 is None :
|
||||
return 1
|
||||
|
||||
return cmp(period1.getDuration(), period2.getDuration())
|
||||
|
||||
|
||||
def __str__(self):
|
||||
buffer = StringIO.StringIO()
|
||||
|
||||
|
@ -172,3 +141,86 @@ class DataTime(object):
|
|||
strVal = buffer.getvalue()
|
||||
buffer.close()
|
||||
return strVal
|
||||
|
||||
def __repr__(self):
|
||||
return "<DataTime instance: " + str(self) + " >"
|
||||
|
||||
def __hash__(self):
|
||||
hashCode = hash(self.refTime) ^ hash(self.fcstTime)
|
||||
if self.validPeriod is not None and self.validPeriod.isValid():
|
||||
hashCode ^= hash(self.validPeriod.getStart())
|
||||
hashCode ^= hash(self.validPeriod.getEnd())
|
||||
hashCode ^= hash(self.levelValue)
|
||||
return hashCode
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(self) != type(other):
|
||||
return False
|
||||
|
||||
if other.getRefTime() is None:
|
||||
return self.fcstTime == other.fcstTime
|
||||
|
||||
dataTime1 = (self.refTime, self.fcstTime, self.validPeriod, self.levelValue)
|
||||
dataTime2 = (other.refTime, other.fcstTime, other.validPeriod, other.levelValue)
|
||||
return dataTime1 == dataTime2
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
|
||||
myValidTime = self.getRefTime().getTime() + self.getFcstTime()
|
||||
otherValidTime = other.getRefTime().getTime() + other.getFcstTime()
|
||||
if myValidTime < otherValidTime:
|
||||
return True
|
||||
|
||||
if self.fcstTime < other.fcstTime:
|
||||
return True
|
||||
|
||||
if self.levelValue < other.levelValue:
|
||||
return True
|
||||
|
||||
myValidPeriod = self.validPeriod
|
||||
otherValidPeriod = other.validPeriod
|
||||
if myValidPeriod != otherValidPeriod:
|
||||
if myValidPeriod.duration() < otherValidPeriod.duration():
|
||||
return True
|
||||
return myValidPeriod.getStartInMillis() < otherValidPeriod.getStartInMillis()
|
||||
return False
|
||||
|
||||
def __le__(self, other):
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
|
||||
return self.__lt__(other) or self.__eq__(other)
|
||||
|
||||
def __gt__(self, other):
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
|
||||
myValidTime = self.getRefTime().getTime() + self.getFcstTime()
|
||||
otherValidTime = other.getRefTime().getTime() + other.getFcstTime()
|
||||
if myValidTime > otherValidTime:
|
||||
return True
|
||||
|
||||
if self.fcstTime > other.fcstTime:
|
||||
return True
|
||||
|
||||
if self.levelValue > other.levelValue:
|
||||
return True
|
||||
|
||||
myValidPeriod = self.validPeriod
|
||||
otherValidPeriod = other.validPeriod
|
||||
if myValidPeriod != otherValidPeriod:
|
||||
if myValidPeriod.duration() > otherValidPeriod.duration():
|
||||
return True
|
||||
return myValidPeriod.getStartInMillis() > otherValidPeriod.getStartInMillis()
|
||||
return False
|
||||
|
||||
def __ge__(self, other):
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
|
||||
return self.__gt__(other) or self.__eq__(other)
|
|
@ -28,6 +28,7 @@
|
|||
# ??/??/?? xxxxxxxx Initial Creation.
|
||||
# 01/22/14 2667 bclement fixed millisecond support
|
||||
# 02/28/14 2667 bclement constructor can take extra micros for start and end
|
||||
# 06/24/15 4480 dgilling fix __eq__.
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -51,7 +52,15 @@ class TimeRange(object):
|
|||
return "(" + self.start.strftime("%b %d %y %H:%M:%S %Z") + ", " + self.end.strftime("%b %d %y %H:%M:%S %Z") + ")"
|
||||
|
||||
def __eq__(self, other):
|
||||
return ((self.start == other.start) and (self.end == other.end))
|
||||
if type(self) != type(other):
|
||||
return False
|
||||
|
||||
if self.isValid() and other.isValid():
|
||||
return self.getStart() == other.getStart() and self.getEnd() == other.getEnd()
|
||||
elif not self.isValid() and not other.isValid():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return (not self.__eq__(other))
|
||||
|
@ -132,7 +141,7 @@ class TimeRange(object):
|
|||
return convTime == self.start
|
||||
|
||||
def isValid(self):
|
||||
return (self.start != self.end)
|
||||
return bool(self.start != self.end)
|
||||
|
||||
def overlaps(self, timeRange):
|
||||
return (timeRange.contains(self.start) or self.contains(timeRange.start))
|
||||
|
|
|
@ -18,25 +18,22 @@
|
|||
# further licensing information.
|
||||
##
|
||||
|
||||
## NOTE: This is a dummy class that is only used for deserialization
|
||||
## support. Further work required if it is need in the pure Python
|
||||
## environment.
|
||||
# File auto-generated against equivalent DynamicSerialize Java class
|
||||
# and then modified post-generation to add additional features to better
|
||||
# match Java implementation.
|
||||
#
|
||||
# SOFTWARE HISTORY
|
||||
#
|
||||
# Date Ticket# Engineer Description
|
||||
# ------------ ---------- ----------- --------------------------
|
||||
# ??/??/?? xxxxxxxx Initial Creation.
|
||||
# 06/24/15 4480 dgilling implement based on Date class.
|
||||
#
|
||||
|
||||
class Timestamp(object):
|
||||
from dynamicserialize.dstypes.java.util import Date
|
||||
|
||||
|
||||
class Timestamp(Date):
|
||||
|
||||
def __init__(self, time=None):
|
||||
self.time = time
|
||||
|
||||
def getTime(self):
|
||||
return self.time
|
||||
|
||||
def setTime(self, timeInMillis):
|
||||
self.time = timeInMillis
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
from time import gmtime, strftime
|
||||
|
||||
return strftime("%b %d %y %H:%M:%S GMT", gmtime(self.time/1000.0))
|
||||
super(Timestamp, self).__init__(time)
|
||||
|
|
|
@ -24,14 +24,17 @@
|
|||
# Date Ticket# Engineer Description
|
||||
# ------------ ---------- ----------- --------------------------
|
||||
# 04/28/2015 4027 randerso Added optional construction parameter to set the time
|
||||
# 06/26/2015 4480 dgilling Implement __eq__ and __hash__.
|
||||
#
|
||||
##
|
||||
|
||||
from time import gmtime, strftime
|
||||
|
||||
|
||||
class Date(object):
|
||||
|
||||
def __init__(self, timeInMillis=None):
|
||||
self.time = None
|
||||
self.time = timeInMillis
|
||||
|
||||
def getTime(self):
|
||||
return self.time
|
||||
|
@ -43,6 +46,13 @@ class Date(object):
|
|||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
from time import gmtime, strftime
|
||||
|
||||
return strftime("%b %d %y %H:%M:%S GMT", gmtime(self.time/1000.0))
|
||||
return strftime("%b %d %y %H:%M:%S GMT", gmtime(self.time/1000.0))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.time == other.time
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.time)
|
||||
|
|
107
pythonPackages/ufpy/DateTimeConverter.py
Normal file
107
pythonPackages/ufpy/DateTimeConverter.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
# #
|
||||
# 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.
|
||||
# #
|
||||
|
||||
#
|
||||
# Functions for converting between the various "Java" dynamic serialize types
|
||||
# used by EDEX to the native python time datetime.
|
||||
#
|
||||
#
|
||||
# SOFTWARE HISTORY
|
||||
#
|
||||
# Date Ticket# Engineer Description
|
||||
# ------------ ---------- ----------- --------------------------
|
||||
# 06/24/15 #4480 dgilling Initial Creation.
|
||||
#
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
from dynamicserialize.dstypes.java.util import Date
|
||||
from dynamicserialize.dstypes.java.sql import Timestamp
|
||||
from dynamicserialize.dstypes.com.raytheon.uf.common.time import TimeRange
|
||||
|
||||
|
||||
MAX_TIME = pow(2, 31) - 1
|
||||
MICROS_IN_SECOND = 1000000
|
||||
|
||||
|
||||
def convertToDateTime(timeArg):
|
||||
"""
|
||||
Converts the given object to a python datetime object. Supports native
|
||||
python representations like datetime and struct_time, but also
|
||||
the dynamicserialize types like Date and Timestamp. Raises TypeError
|
||||
if no conversion can be performed.
|
||||
|
||||
Args:
|
||||
timeArg: a python object representing a date and time. Supported
|
||||
types include datetime, struct_time, float, int, long and the
|
||||
dynamicserialize types Date and Timestamp.
|
||||
|
||||
Returns:
|
||||
A datetime that represents the same date/time as the passed in object.
|
||||
"""
|
||||
if isinstance(timeArg, datetime.datetime):
|
||||
return timeArg
|
||||
elif isinstance(timeArg, time.struct_time):
|
||||
return datetime.datetime(*timeArg[:6])
|
||||
elif isinstance(timeArg, float):
|
||||
# seconds as float, should be avoided due to floating point errors
|
||||
totalSecs = long(timeArg)
|
||||
micros = int((timeArg - totalSecs) * MICROS_IN_SECOND)
|
||||
return _convertSecsAndMicros(totalSecs, micros)
|
||||
elif isinstance(timeArg, (int, long)):
|
||||
# seconds as integer
|
||||
totalSecs = timeArg
|
||||
return _convertSecsAndMicros(totalSecs, 0)
|
||||
elif isinstance(timeArg, (Date, Timestamp)):
|
||||
totalSecs = timeArg.getTime()
|
||||
return _convertSecsAndMicros(totalSecs, 0)
|
||||
else:
|
||||
objType = str(type(timeArg))
|
||||
raise TypeError("Cannot convert object of type " + objType + " to datetime.")
|
||||
|
||||
def _convertSecsAndMicros(seconds, micros):
|
||||
if seconds < MAX_TIME:
|
||||
rval = datetime.datetime.utcfromtimestamp(seconds)
|
||||
else:
|
||||
extraTime = datetime.timedelta(seconds=(seconds - MAX_TIME))
|
||||
rval = datetime.datetime.utcfromtimestamp(MAX_TIME) + extraTime
|
||||
return rval.replace(microsecond=micros)
|
||||
|
||||
def constructTimeRange(*args):
|
||||
"""
|
||||
Builds a python dynamicserialize TimeRange object from the given
|
||||
arguments.
|
||||
|
||||
Args:
|
||||
args*: must be a TimeRange or a pair of objects that can be
|
||||
converted to a datetime via convertToDateTime().
|
||||
|
||||
Returns:
|
||||
A TimeRange.
|
||||
"""
|
||||
|
||||
if len(args) == 1 and isinstance(args[0], TimeRange):
|
||||
return args[0]
|
||||
if len(args) != 2:
|
||||
raise TypeError("constructTimeRange takes exactly 2 arguments, " + str(len(args)) + " provided.")
|
||||
startTime = convertToDateTime(args[0])
|
||||
endTime = convertToDateTime(args[1])
|
||||
return TimeRange(startTime, endTime)
|
283
pythonPackages/ufpy/dataaccess/SoundingsSupport.py
Normal file
283
pythonPackages/ufpy/dataaccess/SoundingsSupport.py
Normal file
|
@ -0,0 +1,283 @@
|
|||
# #
|
||||
# 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.
|
||||
# #
|
||||
|
||||
#
|
||||
# Classes for retrieving soundings based on gridded data from the Data Access
|
||||
# Framework
|
||||
#
|
||||
#
|
||||
#
|
||||
# SOFTWARE HISTORY
|
||||
#
|
||||
# Date Ticket# Engineer Description
|
||||
# ------------ ---------- ----------- --------------------------
|
||||
# 06/24/15 #4480 dgilling Initial Creation.
|
||||
#
|
||||
|
||||
from collections import defaultdict
|
||||
from shapely.geometry import Point
|
||||
|
||||
from ufpy import DateTimeConverter
|
||||
from ufpy.dataaccess import DataAccessLayer
|
||||
|
||||
from dynamicserialize.dstypes.com.raytheon.uf.common.time import DataTime
|
||||
from dynamicserialize.dstypes.com.raytheon.uf.common.dataplugin.level import Level
|
||||
|
||||
|
||||
def getSounding(modelName, weatherElements, levels, samplePoint, refTime=None, timeRange=None):
|
||||
""""
|
||||
Performs a series of Data Access Framework requests to retrieve a sounding object
|
||||
based on the specified request parameters.
|
||||
|
||||
Args:
|
||||
modelName: the grid model datasetid to use as the basis of the sounding.
|
||||
weatherElements: a list of parameters to return in the sounding.
|
||||
levels: a list of levels to sample the given weather elements at
|
||||
samplePoint: a lat/lon pair to perform the sampling of data at.
|
||||
refTime: (optional) the grid model reference time to use for the sounding.
|
||||
If not specified, the latest ref time in the system will be used.
|
||||
timeRange: (optional) a TimeRange to specify which forecast hours to use.
|
||||
If not specified, will default to all forecast hours.
|
||||
|
||||
Returns:
|
||||
A _SoundingCube instance, which acts a 3-tiered dictionary, keyed
|
||||
by DataTime, then by level and finally by weather element. If no
|
||||
data is available for the given request parameters, None is returned.
|
||||
"""
|
||||
|
||||
(locationNames, parameters, levels, envelope, refTime, timeRange) = \
|
||||
__sanitizeInputs(modelName, weatherElements, levels, samplePoint, refTime, timeRange)
|
||||
|
||||
requestArgs = { 'datatype' : 'grid',
|
||||
'locationNames' : locationNames,
|
||||
'parameters' : parameters,
|
||||
'levels' : levels,
|
||||
'envelope' : envelope,
|
||||
}
|
||||
|
||||
req = DataAccessLayer.newDataRequest(**requestArgs)
|
||||
|
||||
forecastHours = __determineForecastHours(req, refTime, timeRange)
|
||||
if not forecastHours:
|
||||
return None
|
||||
|
||||
response = DataAccessLayer.getGeometryData(req, forecastHours)
|
||||
soundingObject = _SoundingCube(response)
|
||||
|
||||
return soundingObject
|
||||
|
||||
def setEDEXHost(host):
|
||||
"""
|
||||
Changes the EDEX host the Data Access Framework is communicating with.
|
||||
|
||||
Args:
|
||||
host: the EDEX host to connect to
|
||||
"""
|
||||
|
||||
if host:
|
||||
DataAccessLayer.changeEDEXHost(str(host))
|
||||
|
||||
def __sanitizeInputs(modelName, weatherElements, levels, samplePoint, refTime, timeRange):
|
||||
locationNames = [str(modelName)]
|
||||
parameters = __buildStringList(weatherElements)
|
||||
levels = __buildStringList(levels)
|
||||
envelope = Point(samplePoint)
|
||||
if refTime is not None:
|
||||
refTime = DataTime(refTime=DateTimeConverter.convertToDateTime(refTime))
|
||||
if timeRange is not None:
|
||||
timeRange = DateTimeConverter.constructTimeRange(*timeRange)
|
||||
return (locationNames, parameters, levels, envelope, refTime, timeRange)
|
||||
|
||||
def __determineForecastHours(request, refTime, timeRange):
|
||||
dataTimes = DataAccessLayer.getAvailableTimes(request, False)
|
||||
timesGen = [(DataTime(refTime=dataTime.getRefTime()), dataTime) for dataTime in dataTimes]
|
||||
dataTimesMap = defaultdict(list)
|
||||
for baseTime, dataTime in timesGen:
|
||||
dataTimesMap[baseTime].append(dataTime)
|
||||
|
||||
if refTime is None:
|
||||
refTime = max(dataTimesMap.keys())
|
||||
|
||||
forecastHours = dataTimesMap[refTime]
|
||||
if timeRange is None:
|
||||
return forecastHours
|
||||
else:
|
||||
return [forecastHour for forecastHour in forecastHours if timeRange.contains(forecastHour.getValidPeriod())]
|
||||
|
||||
def __buildStringList(param):
|
||||
if __notStringIter(param):
|
||||
return [str(item) for item in param]
|
||||
else:
|
||||
return [str(param)]
|
||||
|
||||
def __notStringIter(iterable):
|
||||
if not isinstance(iterable, basestring):
|
||||
try:
|
||||
iter(iterable)
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
class _SoundingCube(object):
|
||||
"""
|
||||
The top-level sounding object returned when calling SoundingsSupport.getSounding.
|
||||
|
||||
This object acts as a 3-tiered dict which is keyed by time then level
|
||||
then parameter name. Calling times() will return all valid keys into this
|
||||
object.
|
||||
"""
|
||||
|
||||
def __init__(self, geometryDataObjects):
|
||||
self._dataDict = {}
|
||||
self._sortedTimes = []
|
||||
if geometryDataObjects:
|
||||
for geometryData in geometryDataObjects:
|
||||
dataTime = geometryData.getDataTime()
|
||||
level = geometryData.getLevel()
|
||||
for parameter in geometryData.getParameters():
|
||||
self.__addItem(parameter, dataTime, level, geometryData.getNumber(parameter))
|
||||
|
||||
def __addItem(self, parameter, dataTime, level, value):
|
||||
timeLayer = self._dataDict.get(dataTime, _SoundingTimeLayer(dataTime))
|
||||
self._dataDict[dataTime] = timeLayer
|
||||
timeLayer._addItem(parameter, level, value)
|
||||
if dataTime not in self._sortedTimes:
|
||||
self._sortedTimes.append(dataTime)
|
||||
self._sortedTimes.sort()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._dataDict[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._dataDict)
|
||||
|
||||
def times(self):
|
||||
"""
|
||||
Returns the valid times for this sounding.
|
||||
|
||||
Returns:
|
||||
A list containing the valid DataTimes for this sounding in order.
|
||||
"""
|
||||
return self._sortedTimes
|
||||
|
||||
|
||||
class _SoundingTimeLayer(object):
|
||||
"""
|
||||
The second-level sounding object returned when calling SoundingsSupport.getSounding.
|
||||
|
||||
This object acts as a 2-tiered dict which is keyed by level then parameter
|
||||
name. Calling levels() will return all valid keys into this
|
||||
object. Calling time() will return the DataTime for this particular layer.
|
||||
"""
|
||||
|
||||
def __init__(self, dataTime):
|
||||
self._dataTime = dataTime
|
||||
self._dataDict = {}
|
||||
|
||||
def _addItem(self, parameter, level, value):
|
||||
asString = str(level)
|
||||
levelLayer = self._dataDict.get(asString, _SoundingTimeAndLevelLayer(self._dataTime, asString))
|
||||
levelLayer._addItem(parameter, value)
|
||||
self._dataDict[asString] = levelLayer
|
||||
|
||||
def __getitem__(self, key):
|
||||
asString = str(key)
|
||||
if asString in self._dataDict:
|
||||
return self._dataDict[asString]
|
||||
else:
|
||||
raise KeyError("Level " + str(key) + " is not a valid level for this sounding.")
|
||||
|
||||
def __len__(self):
|
||||
return len(self._dataDict)
|
||||
|
||||
def time(self):
|
||||
"""
|
||||
Returns the DataTime for this sounding cube layer.
|
||||
|
||||
Returns:
|
||||
The DataTime for this sounding layer.
|
||||
"""
|
||||
return self._dataTime
|
||||
|
||||
def levels(self):
|
||||
"""
|
||||
Returns the valid levels for this sounding.
|
||||
|
||||
Returns:
|
||||
A list containing the valid levels for this sounding in order of
|
||||
closest to surface to highest from surface.
|
||||
"""
|
||||
sortedLevels = [Level(level) for level in self._dataDict.keys()]
|
||||
sortedLevels.sort()
|
||||
return [str(level) for level in sortedLevels]
|
||||
|
||||
|
||||
class _SoundingTimeAndLevelLayer(object):
|
||||
"""
|
||||
The bottom-level sounding object returned when calling SoundingsSupport.getSounding.
|
||||
|
||||
This object acts as a dict which is keyed by parameter name. Calling
|
||||
parameters() will return all valid keys into this object. Calling time()
|
||||
will return the DataTime for this particular layer. Calling level() will
|
||||
return the level for this layer.
|
||||
"""
|
||||
|
||||
def __init__(self, time, level):
|
||||
self._time = time
|
||||
self._level = level
|
||||
self._parameters = {}
|
||||
|
||||
def _addItem(self, parameter, value):
|
||||
self._parameters[parameter] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._parameters[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._parameters)
|
||||
|
||||
def level(self):
|
||||
"""
|
||||
Returns the level for this sounding cube layer.
|
||||
|
||||
Returns:
|
||||
The level for this sounding layer.
|
||||
"""
|
||||
return self._level
|
||||
|
||||
def parameters(self):
|
||||
"""
|
||||
Returns the valid parameters for this sounding.
|
||||
|
||||
Returns:
|
||||
A list containing the valid parameter names.
|
||||
"""
|
||||
return list(self._parameters.keys())
|
||||
|
||||
def time(self):
|
||||
"""
|
||||
Returns the DataTime for this sounding cube layer.
|
||||
|
||||
Returns:
|
||||
The DataTime for this sounding layer.
|
||||
"""
|
||||
return self._time
|
Loading…
Add table
Reference in a new issue