1
2 __version__ = '$Revision: 5234 $'.split()[1]
3 __date__ = '$Date: 2006-12-20 09:05:56 -0500 (Wed, 20 Dec 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 @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__ myparser 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
146 - def hasSensor(self,name="Water Level", status=True, sensorID=None,DCP=None):
147 '''
148 Return true if the station has that particular sensor type
149 '''
150
151
152
153 for p in self.parameters:
154
155 if name and p['name'] !=name: continue
156 if status and p['status'] !=status: continue
157 if sensorID and p['sensorID']!=sensorID: continue
158 if DCP and p['DCP'] !=DCP: continue
159 return True
160
161 return False
162
163
165 f,params = self.fields,self.parameters
166 print f['name'],':',f
167 for p in params:
168 print ' ',p
169
170
171
172
173
175 '''
176 Custom wrapper around the ActiveStations that allows the system to
177 fail back to a precaptured list of stations and instruments
178 '''
179
180 SERVER_ADDR = "opendap.co-ops.nos.noaa.gov"
181 '''Host name for the NOAA Axis soap server'''
182 SERVER_PORT = 80
183 '''Axis sits on the normal Apache http port 80'''
184 NS = "http://opendap.co-ops.nos.noaa.gov/axis/services/ActiveStations"
185 '''The exact location of the soap call. This may be ignored.'''
186
187
188 - def __init__(self,allowCache=True, forceCache=False):
189 '''
190 Fetch the stations from the NOAA web services or a cache.
191
192 @param allowCache: if true, the class will fallback on a
193 precaptured list of stations.
194 '''
195 xml=None
196 if forceCache:
197 xml = open('stations-soap.xml').read()
198 else:
199 if allowCache:
200 try:
201 xml = self.getStationXmlFromSoap()
202 except:
203 xml = open('stations-soap.xml').read()
204 else:
205 xml = self.getStationXmlFromSoap()
206
207 xml = stripNameSpaces(xml)
208
209 from lxml import etree
210 from StringIO import StringIO
211
212
213 self.stationsET = etree.parse(StringIO(xml)).getroot()[0][0][0]
214
215
216
217
219 '''
220 Get a nice lookup table of station ID to name translations
221 '''
222 stationsDict={}
223
224
225 for station in self.stationsET.xpath('station'):
226 stationsDict[station.attrib['ID']] = station.attrib['name']
227 return stationsDict
228
230 '''
231 Specify a bounding box and get the points in that region
232 '''
233 stations=[]
234
235 for station in self.stationsET.xpath('station'):
236
237 lonStr = station.xpath('metadata/location/long')[0].text
238 latStr = station.xpath('metadata/location/lat')[0].text
239
240 try:
241 lon = lonlatText2decimal(lonStr)
242 lat = lonlatText2decimal(latStr)
243
244 if lon >= lowerleft[0] and lon <= upperright[0] and lat >= lowerleft[1] and lat <= upperright[1]:
245
246 stations.append(station.attrib['ID'])
247 except IndexError:
248
249 pass
250
251 return stations
252
253
255 '''
256 Return a station class object
257 '''
258 xpathExpr = "station[@ID='"+ID+"']"
259
260 stationTree = self.stationsET.xpath(xpathExpr)[0]
261 station = Station(stationTree)
262 return station
263
264
266 '''
267 Speak soap to the NOAA axis server and return the SOAP xml.
268 The xmlns make lxml do weird things.
269
270 '''
271
272 BODY_TEMPLATE = '''<SOAP-ENV:Envelope
273 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
274 xmlns:s="'''+NS+'''"
275 xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
276 xmlns:xsd="http://www.w3.org/1999/XMLSchema"
277 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
278 <SOAP-ENV:Body>
279 <s:getActiveStations>
280 </s:getActiveStations>
281 </SOAP-ENV:Body>
282 </SOAP-ENV:Envelope>'''
283
284 body = BODY_TEMPLATE
285 blen = len(body)
286 requestor = httplib.HTTP(SERVER_ADDR, SERVER_PORT)
287 requestor.putrequest("POST", "/axis/services/ActiveStations")
288 requestor.putheader("Host", SERVER_ADDR)
289 requestor.putheader("Content-Type", 'text/plain; charset="utf-8"')
290 requestor.putheader("Content-Length", str(blen))
291 requestor.putheader("SOAPAction", NS)
292 requestor.endheaders()
293 requestor.send(body)
294 (status_code, message, reply_headers) = requestor.getreply()
295 reply_body = requestor.getfile().read()
296
297
298
299
300 return reply_body
301
302
303
304 hamptonRoadsBBox=[(-77.5,36.5),(-74.5,38.0)]
305
307 '''
308 Return a list of Station Class objects. Hides the fetch of the Active Stations
309 '''
310 s = ActiveStations(forceCache=True)
311
312
313
314
315
316 stationIDs = s.getStationsInBBox(ll,ur)
317
318 stations=[]
319 for stationID in stationIDs:
320 station = s.getStation(stationID)
321 if station.hasSensor('Water Level'):
322
323 stations.append(station)
324
325
326 return stations
327
328 if __name__ == '__main__':
329 from optparse import OptionParser
330 myparser = OptionParser(usage="%prog [options]",version="%prog "+__version__)
331 myparser.add_option('--test','--doc-test',dest='doctest',default=False,action='store_true',
332 help='run the documentation tests')
333
334 (options,args) = myparser.parse_args()
335
336 success=True
337
338 if options.doctest:
339 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
340 sys.argv= [sys.argv[0]]
341
342 import doctest
343 numfail,numtests=doctest.testmod()
344 if numfail==0: print 'ok'
345 else:
346 print 'FAILED'
347 success=False
348
349 hrStations = getWaterLevelStationsBBox(hamptonRoadsBBox[0],hamptonRoadsBBox[1])
350 print [ (s.getName(),s.getID()) for s in hrStations]
351 print [s.getID() for s in hrStations]
352 del hrStations
353
354 if not success:
355 sys.exit('Something Failed')
356