Package noaadata :: Module stations
[hide private]
[frames] | no frames]

Source Code for Module noaadata.stations

  1  #!/usr/bin/env python 
  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  #import os, shutil 
 35  #import time 
 36  #import socket 
 37  #import thread 
 38  #import select 
 39  #import tty,termios 
 40  #import calendar 
 41   
42 -def getActiveStationsSoappy(debug=False):
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' # This really can be anything. It is ignored 68 server = SOAPProxy(url,namespace) 69 if debug: server.config.debug=1 70 response = server.getActiveStations() 71 return response
72 73
74 -def stripNameSpaces(xmlString):
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 # Station 105 ###################################################################### 106
107 -class Station:
108 ''' 109 A single station 110 '''
111 - def __init__(self,et):
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 # date_established 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
142 - def getName(self):
143 return self.fields['name']
144 - def getID(self):
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 # for p in self.parameters: 153 # if name and p['name']==name: return True 154 for p in self.parameters: 155 # FIX: make this more general 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
165 - def printMe(self):
166 f,params = self.fields,self.parameters 167 print f['name'],':',f 168 for p in params: 169 print ' ',p
170 171 ###################################################################### 172 # ActiveStations class 173 ###################################################################### 174
175 -class ActiveStations:
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 # Dig past the top wrappers. FIX: use xpath instead to get the ActiveStations node 214 self.stationsET = etree.parse(StringIO(xml)).getroot()[0][0][0]
215 216 #print stationsET.tag 217 #print 'stationsET', [ el.tag for el in stationsET ] 218
219 - def getStationsNameNumb(self):
220 ''' 221 Get a nice lookup table of station ID to name translations 222 ''' 223 stationsDict={} 224 # for loc in self.stationsET.xpath('station/metadata/location'): 225 # print [ el.tag for el in loc ] 226 for station in self.stationsET.xpath('station'): 227 stationsDict[station.attrib['ID']] = station.attrib['name'] 228 return stationsDict
229
230 - def getStationsInBBox(self, lowerleft, upperright):
231 ''' 232 Specify a bounding box and get the points in that region 233 ''' 234 stations=[] 235 # FIX: assert the bounding box is sane 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 #print 'pos strings',lonStr, latStr 241 try: 242 lon = lonlatText2decimal(lonStr) 243 lat = lonlatText2decimal(latStr) 244 #print lon,lat, 'is it in ',lowerleft,upperright 245 if lon >= lowerleft[0] and lon <= upperright[0] and lat >= lowerleft[1] and lat <= upperright[1]: 246 #print 'adding station',station.attrib['ID'] 247 stations.append(station.attrib['ID']) 248 except IndexError: 249 # Must have been empty 250 pass 251 252 return stations
253 254
255 - def getStation(self,ID):
256 ''' 257 Return a station class object 258 ''' 259 xpathExpr = "station[@ID='"+ID+"']" 260 #print xpathExpr 261 stationTree = self.stationsET.xpath(xpathExpr)[0] 262 station = Station(stationTree) 263 return station
264 265
266 - def getStationXmlFromSoap(self):
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 #print "status code:", status_code 299 #print "status message:", message 300 #print "HTTP reply body:\n", reply_body 301 return reply_body
302 303 #def 304 305 hamptonRoadsBBox=[(-77.5,36.5),(-74.5,38.0)] 306
307 -def getWaterLevelStationsBBox(ll,ur):
308 ''' 309 Return a list of Station Class objects. Hides the fetch of the Active Stations 310 ''' 311 s = ActiveStations(forceCache=True) 312 #s.getStationsNameNumb() 313 #print 'Found stations: ',s.getStationsInBBox((-159.5,21),(-159.0,22)) 314 #aStation = s.getStation('1611400') 315 #print aStation.fields, aStation.parameters 316 317 stationIDs = s.getStationsInBBox(ll,ur) 318 #print stationIDs 319 stations=[] 320 for stationID in stationIDs: 321 station = s.getStation(stationID) 322 if station.hasSensor('Water Level'): 323 # station.printMe() 324 stations.append(station) 325 # else: 326 # print 'NO WATER LEVEL' 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 #verbosity.addVerbosityOptions(myparser) 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 #if options.verbosity>=VERBOSE: sys.argv.append('-v') 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