1
2 __version__ = '$Revision: 4763 $'.split()[1]
3 __date__ = '$Date: 2006-09-19 15:34:31 -0400 (Tue, 19 Sep 2006) $'.split()[1]
4 __author__ = 'Kurt Schwehr'
5
6 __doc__='''
7 Handling code for noaa stations data.
8
9 The Web Services Description Language (WSDL) definition for the
10 query/response from the NOAA Axis server.
11
12
13 U{<kurt@ccom.unh.edu>}
14
15 @see: U{NOAA Axis page<http://opendap.co-ops.nos.noaa.gov/axis/>}
16 @see: U{WSDL<http://opendap.co-ops.nos.noaa.gov/axis/services/ActiveStations?wsdl>}
17 @see: U{SOAP XML request<http://opendap.co-ops.nos.noaa.gov/axis/webservices/activestations/samples/request.xml>}
18 @see: U{SOAP XML response<http://opendap.co-ops.nos.noaa.gov/axis/webservices/activestations/samples/response.xml>}
19 @see: U{Java SOAP code<http://opendap.co-ops.nos.noaa.gov/axis/webservices/activestations/samples/client.html>}
20 @see: U{Web interface<http://opendap.co-ops.nos.noaa.gov/axis/webservices/activestations/index.jsp>}
21 @see: U{XPathTutorial<http://www.zvon.org/xxl/XPathTutorial/General/examples.html>}
22 @see: U{python lxml<http://codespeak.net/lxml/>}
23
24 @author: '''+__author__+'''
25 @version: ''' + __version__ +'''
26 @copyright: 2006
27 @var __date__: Date of last svn commit
28 @undocumented: __version__ __author__ __doc__ myparser success
29
30 '''
31
32 import sys, httplib
33
34
35
36
37
38
39
40
41
43 '''
44 Use the old SOAPpy interface to get the stations
45
46 Here is the results for one station:
47
48 print response.station[1]
49
50 <SOAPpy.Types.structType station at 20681072>: {'parameter': ['', ''], 'metadata': <SOAPpy.Types.structType metadata at 20683272>: {'date_established': '1954-11-24', 'location': <SOAPpy.Types.structType location at 20635240>: {'lat': '21 57.3 N', 'state': 'HI', 'long': '159 21.4 W'}}}
51
52
53
54 >>> response = getActiveStationsSoappy()
55 >>> str(response.station[1].metadata.location.lat)
56 '21 57.3 N'
57 >>> str(response.station[1].metadata.location.long)
58 '159 21.4 W'
59 >>> str(response.station[1].metadata.location.state)
60 'HI'
61
62 @param debug: set to true to see more information about the transaction
63 @return: a large typestring
64 '''
65 from SOAPpy import SOAPProxy
66 url = 'http://opendap.co-ops.nos.noaa.gov/axis/services/ActiveStations'
67 namespace='urn:ActiveStations'
68 server = SOAPProxy(url,namespace)
69 if debug: server.config.debug=1
70 response = server.getActiveStations()
71 return response
72
73
75 '''
76 Nuke the xmlns sections. They cause lxml to make strange output.
77
78 @bug: would be better to use regex to nuke the xmlns. Currently very brittle. FIX!
79 '''
80 xml = xmlString
81 tail = 'XMLSchema-instance"'
82 xml2=xml[:xml.find('xmlns')]+xml[xml.find(tail)+len(tail):]
83 tail = 'wsdl"'
84 xml3 = xml2[:xml2.find('xmlns')]+xml2[xml2.find(tail)+len(tail):]
85 return xml3
86
87 -def lonlatText2decimal(lonlatString):
88 '''
89 Convert positions as found in the xml into decimal lon/lat
90
91 >>> lonlatText2decimal('21 57.3 N')
92 21.954999999999998
93 >>> lonlatText2decimal('159 21.4 W')
94 -159.35666666666665
95 '''
96
97 fields = lonlatString.split()
98 val = float(fields[0]) + float(fields[1]) / 60.
99 if fields[2]=='S' or fields[2]=='W':
100 val = -val
101 return val
102
103
104
105
106
108 '''
109 A single station
110 '''
112 '''
113 Create a station object from an element tree
114 @param et: Element Tree for one station
115 '''
116
117 station = et
118 fields = {}
119 fields['name'] = station.attrib['name']
120 fields['ID'] = station.attrib['ID']
121 fields['lonStr'] = station.xpath('metadata/location/long')[0].text
122 fields['latStr'] = station.xpath('metadata/location/lat')[0].text
123 fields['state'] = station.xpath('metadata/location/state')[0].text
124
125 parameters = []
126 for param in station.xpath('parameter'):
127 paramDict={}
128
129 paramDict['DCP'] = param.attrib['DCP']
130 paramDict['name'] = param.attrib['name']
131 paramDict['sensorID'] = param.attrib['sensorID']
132
133 if '0'==param.attrib['status']: paramDict['status']=False
134 else: paramDict['status']=True
135
136 parameters.append(paramDict)
137
138 self.fields = fields
139 self.parameters = parameters
140
141
143 return self.fields['name']
145 return self.fields['ID']
146
147 - def hasSensor(self,name="Water Level", status=True, sensorID=None,DCP=None):
148 '''
149 Return true if the station has that particular sensor type
150 '''
151
152
153
154 for p in self.parameters:
155
156 if name and p['name'] !=name: continue
157 if status and p['status'] !=status: continue
158 if sensorID and p['sensorID']!=sensorID: continue
159 if DCP and p['DCP'] !=DCP: continue
160 return True
161
162 return False
163
164
166 f,params = self.fields,self.parameters
167 print f['name'],':',f
168 for p in params:
169 print ' ',p
170
171
172
173
174
176 '''
177 Custom wrapper around the ActiveStations that allows the system to
178 fail back to a precaptured list of stations and instruments
179 '''
180
181 SERVER_ADDR = "opendap.co-ops.nos.noaa.gov"
182 '''Host name for the NOAA Axis soap server'''
183 SERVER_PORT = 80
184 '''Axis sits on the normal Apache http port 80'''
185 NS = "http://opendap.co-ops.nos.noaa.gov/axis/services/ActiveStations"
186 '''The exact location of the soap call. This may be ignored.'''
187
188
189 - def __init__(self,allowCache=True, forceCache=False):
190 '''
191 Fetch the stations from the NOAA web services or a cache.
192
193 @param allowCache: if true, the class will fallback on a
194 precaptured list of stations.
195 '''
196 xml=None
197 if forceCache:
198 xml = open('stations-soap.xml').read()
199 else:
200 if allowCache:
201 try:
202 xml = self.getStationXmlFromSoap()
203 except:
204 xml = open('stations-soap.xml').read()
205 else:
206 xml = self.getStationXmlFromSoap()
207
208 xml = stripNameSpaces(xml)
209
210 from lxml import etree
211 from StringIO import StringIO
212
213
214 self.stationsET = etree.parse(StringIO(xml)).getroot()[0][0][0]
215
216
217
218
220 '''
221 Get a nice lookup table of station ID to name translations
222 '''
223 stationsDict={}
224
225
226 for station in self.stationsET.xpath('station'):
227 stationsDict[station.attrib['ID']] = station.attrib['name']
228 return stationsDict
229
231 '''
232 Specify a bounding box and get the points in that region
233 '''
234 stations=[]
235
236 for station in self.stationsET.xpath('station'):
237
238 lonStr = station.xpath('metadata/location/long')[0].text
239 latStr = station.xpath('metadata/location/lat')[0].text
240
241 try:
242 lon = lonlatText2decimal(lonStr)
243 lat = lonlatText2decimal(latStr)
244
245 if lon >= lowerleft[0] and lon <= upperright[0] and lat >= lowerleft[1] and lat <= upperright[1]:
246
247 stations.append(station.attrib['ID'])
248 except IndexError:
249
250 pass
251
252 return stations
253
254
256 '''
257 Return a station class object
258 '''
259 xpathExpr = "station[@ID='"+ID+"']"
260
261 stationTree = self.stationsET.xpath(xpathExpr)[0]
262 station = Station(stationTree)
263 return station
264
265
267 '''
268 Speak soap to the NOAA axis server and return the SOAP xml.
269 The xmlns make lxml do weird things.
270
271 '''
272
273 BODY_TEMPLATE = '''<SOAP-ENV:Envelope
274 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
275 xmlns:s="'''+NS+'''"
276 xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
277 xmlns:xsd="http://www.w3.org/1999/XMLSchema"
278 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
279 <SOAP-ENV:Body>
280 <s:getActiveStations>
281 </s:getActiveStations>
282 </SOAP-ENV:Body>
283 </SOAP-ENV:Envelope>'''
284
285 body = BODY_TEMPLATE
286 blen = len(body)
287 requestor = httplib.HTTP(SERVER_ADDR, SERVER_PORT)
288 requestor.putrequest("POST", "/axis/services/ActiveStations")
289 requestor.putheader("Host", SERVER_ADDR)
290 requestor.putheader("Content-Type", 'text/plain; charset="utf-8"')
291 requestor.putheader("Content-Length", str(blen))
292 requestor.putheader("SOAPAction", NS)
293 requestor.endheaders()
294 requestor.send(body)
295 (status_code, message, reply_headers) = requestor.getreply()
296 reply_body = requestor.getfile().read()
297
298
299
300
301 return reply_body
302
303
304
305 hamptonRoadsBBox=[(-77.5,36.5),(-74.5,38.0)]
306
308 '''
309 Return a list of Station Class objects. Hides the fetch of the Active Stations
310 '''
311 s = ActiveStations(forceCache=True)
312
313
314
315
316
317 stationIDs = s.getStationsInBBox(ll,ur)
318
319 stations=[]
320 for stationID in stationIDs:
321 station = s.getStation(stationID)
322 if station.hasSensor('Water Level'):
323
324 stations.append(station)
325
326
327 return stations
328
329 if __name__ == '__main__':
330 from optparse import OptionParser
331 myparser = OptionParser(usage="%prog [options]",version="%prog "+__version__)
332 myparser.add_option('--test','--doc-test',dest='doctest',default=False,action='store_true',
333 help='run the documentation tests')
334
335 (options,args) = myparser.parse_args()
336
337 success=True
338
339 if options.doctest:
340 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
341 sys.argv= [sys.argv[0]]
342
343 import doctest
344 numfail,numtests=doctest.testmod()
345 if numfail==0: print 'ok'
346 else:
347 print 'FAILED'
348 success=False
349
350 hrStations = getWaterLevelStationsBBox(hamptonRoadsBBox[0],hamptonRoadsBBox[1])
351 print [ (s.getName(),s.getID()) for s in hrStations]
352 print [s.getID() for s in hrStations]
353 del hrStations
354
355 if not success:
356 sys.exit('Something Failed')
357