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