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

Source Code for Module noaadata.stations

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