|
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
|
|
|
|