1
|
|
|
# # |
2
|
|
|
# # |
3
|
|
|
|
4
|
|
|
# |
5
|
|
|
# Classes for retrieving soundings based on gridded data from the Data Access |
6
|
|
|
# Framework |
7
|
|
|
# |
8
|
|
|
# |
9
|
|
|
# |
10
|
|
|
# SOFTWARE HISTORY |
11
|
|
|
# |
12
|
|
|
# Date Ticket# Engineer Description |
13
|
|
|
# ------------ ---------- ----------- -------------------------- |
14
|
|
|
# 06/24/15 #4480 dgilling Initial Creation. |
15
|
|
|
# |
16
|
|
|
|
17
|
|
|
from collections import defaultdict |
18
|
|
|
from shapely.geometry import Point |
19
|
|
|
|
20
|
|
|
from awips import DateTimeConverter |
21
|
|
|
from awips.dataaccess import DataAccessLayer |
22
|
|
|
|
23
|
|
|
from dynamicserialize.dstypes.com.raytheon.uf.common.time import DataTime |
24
|
|
|
from dynamicserialize.dstypes.com.raytheon.uf.common.dataplugin.level import Level |
25
|
|
|
|
26
|
|
|
|
27
|
|
View Code Duplication |
def getSounding(modelName, weatherElements, levels, samplePoint, refTime=None, timeRange=None): |
|
|
|
|
28
|
|
|
""" |
29
|
|
|
Performs a series of Data Access Framework requests to retrieve a sounding object |
30
|
|
|
based on the specified request parameters. |
31
|
|
|
|
32
|
|
|
Args: |
33
|
|
|
modelName: the grid model datasetid to use as the basis of the sounding. |
34
|
|
|
weatherElements: a list of parameters to return in the sounding. |
35
|
|
|
levels: a list of levels to sample the given weather elements at |
36
|
|
|
samplePoint: a lat/lon pair to perform the sampling of data at. |
37
|
|
|
refTime: (optional) the grid model reference time to use for the sounding. |
38
|
|
|
If not specified, the latest ref time in the system will be used. |
39
|
|
|
|
40
|
|
|
timeRange: (optional) a TimeRange to specify which forecast hours to use. |
41
|
|
|
If not specified, will default to all forecast hours. |
42
|
|
|
|
43
|
|
|
Returns: |
44
|
|
|
A _SoundingCube instance, which acts a 3-tiered dictionary, keyed |
45
|
|
|
by DataTime, then by level and finally by weather element. If no |
46
|
|
|
data is available for the given request parameters, None is returned. |
47
|
|
|
|
48
|
|
|
""" |
49
|
|
|
|
50
|
|
|
(locationNames, parameters, levels, envelope, refTime, timeRange) = \ |
51
|
|
|
__sanitizeInputs(modelName, weatherElements, levels, samplePoint, refTime, timeRange) |
52
|
|
|
|
53
|
|
|
requestArgs = { 'datatype' : 'grid', |
|
|
|
|
54
|
|
|
'locationNames' : locationNames, |
55
|
|
|
'parameters' : parameters, |
56
|
|
|
'levels' : levels, |
57
|
|
|
'envelope' : envelope, |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
req = DataAccessLayer.newDataRequest(**requestArgs) |
61
|
|
|
|
62
|
|
|
forecastHours = __determineForecastHours(req, refTime, timeRange) |
63
|
|
|
if not forecastHours: |
64
|
|
|
return None |
65
|
|
|
response = DataAccessLayer.getGeometryData(req, forecastHours) |
66
|
|
|
soundingObject = _SoundingCube(response) |
67
|
|
|
|
68
|
|
|
return soundingObject |
69
|
|
|
|
70
|
|
|
def changeEDEXHost(host): |
71
|
|
|
""" |
72
|
|
|
Changes the EDEX host the Data Access Framework is communicating with. |
73
|
|
|
|
74
|
|
|
Args: |
75
|
|
|
host: the EDEX host to connect to |
76
|
|
|
""" |
77
|
|
|
|
78
|
|
|
if host: |
79
|
|
|
DataAccessLayer.changeEDEXHost(str(host)) |
80
|
|
|
|
81
|
|
View Code Duplication |
def __sanitizeInputs(modelName, weatherElements, levels, samplePoint, refTime, timeRange): |
|
|
|
|
82
|
|
|
locationNames = [str(modelName)] |
83
|
|
|
parameters = __buildStringList(weatherElements) |
84
|
|
|
levels = __buildStringList(levels) |
85
|
|
|
envelope = Point(samplePoint) |
86
|
|
|
if refTime is not None: |
87
|
|
|
refTime = DataTime(refTime=DateTimeConverter.convertToDateTime(refTime)) |
88
|
|
|
if timeRange is not None: |
89
|
|
|
timeRange = DateTimeConverter.constructTimeRange(*timeRange) |
90
|
|
|
return (locationNames, parameters, levels, envelope, refTime, timeRange) |
91
|
|
|
|
92
|
|
View Code Duplication |
def __determineForecastHours(request, refTime, timeRange): |
|
|
|
|
93
|
|
|
dataTimes = DataAccessLayer.getAvailableTimes(request, False) |
94
|
|
|
timesGen = [(DataTime(refTime=dataTime.getRefTime()), dataTime) for dataTime in dataTimes] |
95
|
|
|
dataTimesMap = defaultdict(list) |
96
|
|
|
for baseTime, dataTime in timesGen: |
97
|
|
|
dataTimesMap[baseTime].append(dataTime) |
98
|
|
|
|
99
|
|
|
if refTime is None: |
100
|
|
|
refTime = max(dataTimesMap.keys()) |
101
|
|
|
|
102
|
|
|
forecastHours = dataTimesMap[refTime] |
103
|
|
|
if timeRange is None: |
|
|
|
|
104
|
|
|
return forecastHours |
105
|
|
|
else: |
106
|
|
|
return [forecastHour for forecastHour in forecastHours if timeRange.contains(forecastHour.getValidPeriod())] |
|
|
|
|
107
|
|
|
|
108
|
|
|
def __buildStringList(param): |
109
|
|
|
if __notStringIter(param): |
|
|
|
|
110
|
|
|
return [str(item) for item in param] |
111
|
|
|
else: |
112
|
|
|
return [str(param)] |
113
|
|
|
|
114
|
|
|
def __notStringIter(iterable): |
115
|
|
|
if not isinstance(iterable, str): |
116
|
|
|
try: |
117
|
|
|
iter(iterable) |
118
|
|
|
return True |
119
|
|
|
except TypeError: |
120
|
|
|
return False |
121
|
|
|
|
122
|
|
|
|
123
|
|
|
|
124
|
|
View Code Duplication |
class _SoundingCube(object): |
|
|
|
|
125
|
|
|
""" |
126
|
|
|
The top-level sounding object returned when calling SoundingsSupport.getSounding. |
127
|
|
|
|
128
|
|
|
This object acts as a 3-tiered dict which is keyed by time then level |
129
|
|
|
then parameter name. Calling times() will return all valid keys into this |
130
|
|
|
object. |
131
|
|
|
""" |
132
|
|
|
|
133
|
|
|
def __init__(self, geometryDataObjects): |
134
|
|
|
self._dataDict = {} |
135
|
|
|
self._sortedTimes = [] |
136
|
|
|
if geometryDataObjects: |
137
|
|
|
for geometryData in geometryDataObjects: |
138
|
|
|
dataTime = geometryData.getDataTime() |
139
|
|
|
level = geometryData.getLevel() |
140
|
|
|
for parameter in geometryData.getParameters(): |
141
|
|
|
self.__addItem(parameter, dataTime, level, geometryData.getNumber(parameter)) |
142
|
|
|
|
143
|
|
|
def __addItem(self, parameter, dataTime, level, value): |
144
|
|
|
timeLayer = self._dataDict.get(dataTime, _SoundingTimeLayer(dataTime)) |
145
|
|
|
self._dataDict[dataTime] = timeLayer |
146
|
|
|
timeLayer._addItem(parameter, level, value) |
|
|
|
|
147
|
|
|
if dataTime not in self._sortedTimes: |
148
|
|
|
self._sortedTimes.append(dataTime) |
149
|
|
|
self._sortedTimes.sort() |
150
|
|
|
|
151
|
|
|
def __getitem__(self, key): |
152
|
|
|
return self._dataDict[key] |
153
|
|
|
|
154
|
|
|
def __len__(self): |
155
|
|
|
return len(self._dataDict) |
156
|
|
|
|
157
|
|
|
def times(self): |
158
|
|
|
""" |
159
|
|
|
Returns the valid times for this sounding. |
160
|
|
|
|
161
|
|
|
Returns: |
162
|
|
|
A list containing the valid DataTimes for this sounding in order. |
163
|
|
|
""" |
164
|
|
|
return self._sortedTimes |
165
|
|
|
|
166
|
|
|
|
167
|
|
View Code Duplication |
class _SoundingTimeLayer(object): |
|
|
|
|
168
|
|
|
""" |
169
|
|
|
The second-level sounding object returned when calling SoundingsSupport.getSounding. |
170
|
|
|
|
171
|
|
|
This object acts as a 2-tiered dict which is keyed by level then parameter |
172
|
|
|
name. Calling levels() will return all valid keys into this |
173
|
|
|
object. Calling time() will return the DataTime for this particular layer. |
174
|
|
|
""" |
175
|
|
|
|
176
|
|
|
def __init__(self, dataTime): |
177
|
|
|
self._dataTime = dataTime |
178
|
|
|
self._dataDict = {} |
179
|
|
|
|
180
|
|
|
def _addItem(self, parameter, level, value): |
181
|
|
|
asString = str(level) |
182
|
|
|
levelLayer = self._dataDict.get(asString, _SoundingTimeAndLevelLayer(self._dataTime, asString)) |
|
|
|
|
183
|
|
|
levelLayer._addItem(parameter, value) |
|
|
|
|
184
|
|
|
self._dataDict[asString] = levelLayer |
185
|
|
|
|
186
|
|
|
def __getitem__(self, key): |
187
|
|
|
asString = str(key) |
188
|
|
|
if asString in self._dataDict: |
189
|
|
|
return self._dataDict[asString] |
190
|
|
|
else: |
191
|
|
|
raise KeyError("Level " + str(key) + " is not a valid level for this sounding.") |
192
|
|
|
|
193
|
|
|
def __len__(self): |
194
|
|
|
return len(self._dataDict) |
195
|
|
|
|
196
|
|
|
def time(self): |
197
|
|
|
""" |
198
|
|
|
Returns the DataTime for this sounding cube layer. |
199
|
|
|
|
200
|
|
|
Returns: |
201
|
|
|
The DataTime for this sounding layer. |
202
|
|
|
""" |
203
|
|
|
return self._dataTime |
204
|
|
|
|
205
|
|
|
def levels(self): |
206
|
|
|
""" |
207
|
|
|
Returns the valid levels for this sounding. |
208
|
|
|
|
209
|
|
|
Returns: |
210
|
|
|
A list containing the valid levels for this sounding in order of |
211
|
|
|
closest to surface to highest from surface. |
212
|
|
|
""" |
213
|
|
|
sortedLevels = [Level(level) for level in list(self._dataDict.keys())] |
214
|
|
|
sortedLevels.sort() |
215
|
|
|
return [str(level) for level in sortedLevels] |
216
|
|
|
|
217
|
|
|
|
218
|
|
View Code Duplication |
class _SoundingTimeAndLevelLayer(object): |
|
|
|
|
219
|
|
|
""" |
220
|
|
|
The bottom-level sounding object returned when calling SoundingsSupport.getSounding. |
221
|
|
|
|
222
|
|
|
This object acts as a dict which is keyed by parameter name. Calling |
223
|
|
|
parameters() will return all valid keys into this object. Calling time() |
224
|
|
|
will return the DataTime for this particular layer. Calling level() will |
225
|
|
|
return the level for this layer. |
226
|
|
|
""" |
227
|
|
|
|
228
|
|
|
def __init__(self, time, level): |
229
|
|
|
self._time = time |
230
|
|
|
self._level = level |
231
|
|
|
self._parameters = {} |
232
|
|
|
|
233
|
|
|
def _addItem(self, parameter, value): |
234
|
|
|
self._parameters[parameter] = value |
235
|
|
|
|
236
|
|
|
def __getitem__(self, key): |
237
|
|
|
return self._parameters[key] |
238
|
|
|
|
239
|
|
|
def __len__(self): |
240
|
|
|
return len(self._parameters) |
241
|
|
|
|
242
|
|
|
def level(self): |
243
|
|
|
""" |
244
|
|
|
Returns the level for this sounding cube layer. |
245
|
|
|
|
246
|
|
|
Returns: |
247
|
|
|
The level for this sounding layer. |
248
|
|
|
""" |
249
|
|
|
return self._level |
250
|
|
|
|
251
|
|
|
def parameters(self): |
252
|
|
|
""" |
253
|
|
|
Returns the valid parameters for this sounding. |
254
|
|
|
|
255
|
|
|
Returns: |
256
|
|
|
A list containing the valid parameter names. |
257
|
|
|
""" |
258
|
|
|
return list(self._parameters.keys()) |
259
|
|
|
|
260
|
|
|
def time(self): |
261
|
|
|
""" |
262
|
|
|
Returns the DataTime for this sounding cube layer. |
263
|
|
|
|
264
|
|
|
Returns: |
265
|
|
|
The DataTime for this sounding layer. |
266
|
|
|
""" |
267
|
|
|
return self._time |
268
|
|
|
|