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

Source Code for Module noaadata.stations

  1  #!/usr/bin/env python 
  2  __version__ = '$Revision: 6044 $'.split()[1] 
  3  __date__ = '$Date: 2007-04-23 12:43:21 -0700 (Mon, 23 Apr 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 getLon(self):
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
152 - def getLat(self):
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 # for p in self.parameters: 166 # if name and p['name']==name: return True 167 for p in self.parameters: 168 # FIX: make this more general 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
180 - def printMe(self):
181 f,params = self.fields,self.parameters 182 print f['name'],':',f 183 for p in params: 184 print ' ',p
185 186 ###################################################################### 187 # ActiveStations class 188 ###################################################################### 189
190 -class ActiveStations:
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 #print '__file__',__file__ 214 # FIX: is there a __path__ like thing that I can use like ais-py/ais/__init__.py? 215 # FIX: this will not work on Windows!!! 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 # Dig past the top wrappers. FIX: use xpath instead to get the ActiveStations node 233 self.stationsET = etree.parse(StringIO(xml)).getroot()[0][0][0]
234 235 #print stationsET.tag 236 #print 'stationsET', [ el.tag for el in stationsET ] 237
238 - def getStationsNameNumb(self):
239 ''' 240 Get a nice lookup table of station ID to name translations 241 ''' 242 stationsDict={} 243 # for loc in self.stationsET.xpath('station/metadata/location'): 244 # print [ el.tag for el in loc ] 245 for station in self.stationsET.xpath('station'): 246 stationsDict[station.attrib['ID']] = station.attrib['name'] 247 return stationsDict
248
249 - def getStationsInBBox(self, lowerleft, upperright):
250 ''' 251 Specify a bounding box and get the points in that region 252 ''' 253 stations=[] 254 # FIX: assert the bounding box is sane 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 #print 'pos strings',lonStr, latStr 260 try: 261 lon = lonlatText2decimal(lonStr) 262 lat = lonlatText2decimal(latStr) 263 #print lon,lat, 'is it in ',lowerleft,upperright 264 if lon >= lowerleft[0] and lon <= upperright[0] and lat >= lowerleft[1] and lat <= upperright[1]: 265 #print 'adding station',station.attrib['ID'] 266 stations.append(station.attrib['ID']) 267 except IndexError: 268 # Must have been empty 269 pass 270 271 return stations
272 273
274 - def getStation(self,ID):
275 ''' 276 Return a station class object 277 ''' 278 xpathExpr = "station[@ID='"+str(ID)+"']" 279 #print xpathExpr 280 stationTree = self.stationsET.xpath(xpathExpr)[0] 281 station = Station(stationTree) 282 return station
283 284
285 - def getStationXmlFromSoap(self):
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 #print "status code:", status_code 318 #print "status message:", message 319 #print "HTTP reply body:\n", reply_body 320 return reply_body
321 322 #def 323 324 hamptonRoadsBBox=[(-77.5,36.5),(-74.5,38.0)] 325
326 -def getWaterLevelStationsBBox(ll,ur):
327 ''' 328 Return a list of Station Class objects. Hides the fetch of the Active Stations 329 ''' 330 s = ActiveStations(forceCache=True) 331 #s.getStationsNameNumb() 332 #print 'Found stations: ',s.getStationsInBBox((-159.5,21),(-159.0,22)) 333 #aStation = s.getStation('1611400') 334 #print aStation.fields, aStation.parameters 335 336 stationIDs = s.getStationsInBBox(ll,ur) 337 #print stationIDs 338 stations=[] 339 for stationID in stationIDs: 340 station = s.getStation(stationID) 341 if station.hasSensor('Water Level'): 342 # station.printMe() 343 stations.append(station) 344 # else: 345 # print 'NO WATER LEVEL' 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