NCCOOS Trac Projects: Top | Web | Platforms | Processing | Viz | Sprints | Sandbox | (Wind)

Changeset 64

Show
Ignore:
Timestamp:
08/16/07 17:21:18
Author:
cbc
Message:

Miscellaneous clean up and documentation.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • sodar/trunk/sodar/data.py

    r63 r64  
    11#!/usr/bin/python 
    2 """Classes to handle sodar data samples 
     2""" 
     3Classes to handle sodar data samples. 
    34 
    45Sodar data samples are collected into daily files. Each sample consists of a 
    56header followed by an observation for each height. 
     7 
     8The daily file is split into a list (modeled by the class Data) of samples 
     9(modeled by the class Sample) in chronological order. A Data object is 
     10initialized with a string representing the daily file data: 
     11 
     12     dataHandle = open('20070601.dat') 
     13     dataString = data.read() 
     14     dataObject = Data(dataString) 
     15 
     16Each Sample object has attributes for a Header and Body object. The Samples 
     17within a Data object may also be accessed by time using a string of the format 
     18YYYY-MM-DD-HH-MM as in index on the Data object to return the first matching 
     19Sample in the Data object: 
     20 
     21    dataObject[0] # the first Sample object of the day 
     22    dataObject['2007-06-01-09-15'] # the Sample object for 9:15am 
     23    dataObject[15].header # the Header object of the 16th Sample 
     24    dataObject['2007-06-01-09-15'].body # the Body object for 9:15am 
     25 
     26Header objects act as dictionaries. Access each sample-wide parameter of 
     27interest using the header parameter name as a keyword on the Header object: 
     28 
     29    dataObject[15].header['VAL2'] # the number of validations for beam 2 
     30    dataObject['2007-06-01-09-15'].header['SPU3'] # normalized false signal 
     31                                                  # probability on beam 3 
     32    dataObject[0].header['SNR1'] # signal to noise on beam 1 
     33 
     34Consult your Sodar documentation for a complete list of header parameters. 
     35 
     36Body objects act as lists of dictionaries. The dictionaries access 
     37altitude-specific parameters by name as keywords. The dictionaries are in 
     38altitude-ascending order. Each dictionary may also by accessed by indexing with 
     39an altitude string: 
     40 
     41    dataObject[15].body[0] # the data for the lowest altitude, 16th sample 
     42    dataObject['2007-06-01-09-15'].body['70'] # the data for 70 meters 
     43    dataObject[15].body[0]['SPEED'] # wind speed at lowest altitude 
     44    dataObject['2007-06-01-09-15'].body['70']['DIR'] # wind direction 
     45                                                     # at 70 meters 
     46 
     47The body attribute of a Sample object may also be indexed directly on a Sample 
     48object for the most convenient semantics: 
     49 
     50    dataObject[15][0]['SPEED'] # wind speed at lowest altitude, 16th sample 
     51    dataObject['2007-06-01-09-15']['70']['DIR'] # wind direction, 
     52                                                # 70 meters, 9:15am 
    653""" 
    754 
     55__author__ = 'Chris Calloway' 
     56__email__ = 'cbc@unc.edu' 
     57__copyright__ = 'Copyright 2007 UNC-CH Department of Marine Science' 
     58__license__ = 'GPL2' 
     59 
    860import re 
    961 
     62 
    1063class Data(list): 
    11     """Daily sodar file data 
    12  
    13        (a collection of samples) 
    14     """ 
    15     def __init__(self,data): 
    16         super(Data,self).__init__() 
    17         # samples in file are terminated by $ 
     64     
     65    """Daily sodar file data. 
     66        
     67       (A chronologically ordered list of samples.) 
     68    """ 
     69     
     70    def __init__(self, data): 
     71        """Divide daily string into list of Samples separated by $.""" 
     72        super(Data, self).__init__() 
    1873        self.extend([Sample(sample) 
    1974                     for sample in 
    2075                     [sample.strip() for sample in data.split('$')] 
    21                      if sample]) 
    22  
    23     def __getitem__(self,index): 
    24         """allow sample retrieval by sample time in header 
    25         """ 
     76                     if sample.strip()]) 
     77 
     78    def __getitem__(self, index): 
     79        """Allow sample retrieval by Sample time in header.""" 
    2680        try: 
    2781            return super(Data,self).__getitem__(index) 
    2882        except TypeError: 
    29             return self.find(index) 
    30  
    31     def find(self,index): 
    32         """find Sample in Data 
    33  
    34             where sample time of form YYYY-MM-DD-HH-MM 
     83            return self._find(index) 
     84 
     85    def _find(self, index): 
     86        """Find Sample in Data 
     87            
     88           where sample time of form YYYY-MM-DD-HH-MM. 
    3589        """ 
    36         try: 
    37             year,month,day,hour,min = index.split('-') 
     90         
     91        try: 
     92            year,month,day,hour,minute = index.split('-') 
    3893        except ValueError: 
    39             raise ValueError,'Data index by date must be "YYYY-MM-DD-HH-MM"' 
     94            raise ValueError('Data index by date must be "YYYY-MM-DD-HH-MM"') 
    4095        except AttributeError: 
    41             raise AttributeError,'Data index by date must be "YYYY-MM-DD-HH-MM"' 
     96            raise AttributeError('Data index by date must be "YYYY-MM-DD-HH-MM"') 
    4297        for sample in self: 
    43             if sample.header['YEAR'].rjust(4,'0') != year: continue 
    44             if sample.header['MONTH'].rjust(2,'0') != month: continue 
    45             if sample.header['DAY'].rjust(2,'0') != day: continue 
    46             if sample.header['HOUR'].rjust(2,'0') != hour: continue 
    47             if sample.header['MIN'].rjust(2,'0') != min: continue 
    48             return sample 
    49         raise IndexError,'Data index out of range' 
     98            try: 
     99                if sample.header['YEAR'].rjust(4,'0') != year: continue 
     100                if sample.header['MONTH'].rjust(2,'0') != month: continue 
     101                if sample.header['DAY'].rjust(2,'0') != day: continue 
     102                if sample.header['HOUR'].rjust(2,'0') != hour: continue 
     103                if sample.header['MIN'].rjust(2,'0') != minute: continue 
     104                return sample 
     105            except TypeError:   # sample.header may not exist 
     106                continue 
     107        raise IndexError('Data index out of range') 
     108 
    50109 
    51110class Sample(object): 
    52     """A single sample from daily sodar file data 
    53  
    54        (a header and a body) 
    55     """ 
     111     
     112    """A single sample from daily sodar file data. 
     113        
     114       (A header and a body attribute.) 
     115    """ 
     116     
    56117    def __init__(self,sample): 
    57         super(Sample,self).__init__() 
     118        """Separate Sample into Header and Body objects.""" 
     119        super(Sample, self).__init__() 
    58120        # first three groups of lines are the header; rest is body 
    59121        samplePattern = re.compile(r'''(?P<header>.*?\n\n.*?\n\n.*?\n\n) 
     
    61123                                    ''',re.DOTALL | re.VERBOSE) 
    62124        self.__dict__.update(samplePattern.match(sample.strip()).groupdict()) 
    63         # fix for missing keys 
    64         self.header = Header(self.header) 
    65         self.body = Body(self.body) 
    66  
    67     def __getitem__(self,index): 
    68         return self.body[index] 
     125        # self.__dict__.get covers parsing invalid Samples 
     126        self.header = self.__dict__.get('header', None) 
     127        if self.header is not None: 
     128            self.header = Header(self.header) 
     129        self.body = self.__dict__.get('body', None) 
     130        if self.body is not None: 
     131            self.body = Body(self.body) 
     132 
     133    def __getitem__(self, index): 
     134        """Index Sample by body attribute.""" 
     135        try: 
     136            return self.body[index] 
     137        except TypeError:   # sample.body may not exist 
     138            raise IndexError('Sample index out of range') 
     139 
    69140 
    70141class Header(dict): 
    71     """A sodar data sample header 
    72  
    73       (a collection of sample-wide parameters) 
    74     """ 
    75     def __init__(self,header): 
    76         super(Header,self).__init__() 
    77         headerLines = header.split('\n') 
    78         # every other line contains parameter keys; 
    79         # every other line contains parameter values 
     142     
     143    """A sodar data sample header. 
     144 
     145      (A dictionary of sample-wide parameters.) 
     146    """ 
     147     
     148    def __init__(self, header): 
     149         
     150        """Identify discreet header parameter names and values. 
     151            
     152           Every other line contains parameter keys; 
     153           every other line contains parameter values. 
     154        """ 
     155         
     156        super(Header, self).__init__() 
     157        headerLines = [headerLine.strip() 
     158                       for headerLine in header.split('\n') 
     159                       if headerLine.strip()] 
     160        #fix for bad match between names and values 
    80161        self.update(dict(zip(" ".join(headerLines[::2]).split(), 
    81162                             " ".join(headerLines[1::2]).split()))) 
     163         
    82164 
    83165class Body(list): 
    84     """A sodar data sample body 
    85  
    86        (a collection of collections at each altitude) 
    87     """ 
    88     def __init__(self,body): 
    89         super(Body,self).__init__() 
    90         bodyLines = body.split('\n') 
     166     
     167    """A sodar data sample body. 
     168 
     169       (A list of dictionariess at each altitude.) 
     170    """ 
     171     
     172    def __init__(self, body): 
     173         
     174        """Identify discreet body parameter names and values. 
     175            
     176           The first line contains parameter keys; 
     177           the remaining lines contains parameter values, 
     178           one set of parameters for a single altitude per line. 
     179        """ 
     180         
     181        super(Body, self).__init__() 
     182        bodyLines = [bodyLine.strip() 
     183                     for bodyLine in body.split('\n') 
     184                     if bodyLine.strip()] 
    91185        bodyKeys = bodyLines[0].split() 
     186        #fix for bad match between names and values 
    92187        self.extend([dict(zip(bodyKeys, bodyLine.split())) 
    93188                     for bodyLine in bodyLines[1:]]) 
    94189        self.reverse()             
    95190 
    96     def __getitem__(self,index): 
    97         """allow retrieval by altitude string 
    98         """ 
    99         try: 
    100             return super(Body,self).__getitem__(index) 
     191    def __getitem__(self, index): 
     192        """Return altitude data by altitude string.""" 
     193        try: 
     194            return super(Body, self).__getitem__(index) 
    101195        except TypeError: 
    102             return self.find(index) 
    103  
    104     def find(self,index): 
    105         """find altitude data in Body 
    106         """ 
     196            return self._find(index) 
     197 
     198    def _find(self, index): 
     199        """Find altitude data in Body.""" 
    107200        for altitudeData in self: 
    108201            if altitudeData['ALT'] != index: continue 
    109202            return altitudeData 
    110         raise IndexError,'Body index, out of range' 
    111  
    112 def __main(): 
    113     """Process as script from command line 
    114     """ 
     203        raise IndexError('Body index, out of range') 
     204 
     205 
     206def _main(): 
     207    """Process as script from command line.""" 
    115208    import urllib2 
    116209    try: 
    117        data = urllib2.urlopen('http://nemo.isis.unc.edu/data/nccoos/level0/dukeforest/sodar/store/2007-06/20070601.dat') 
    118        data = data.read() 
     210        dataHandle = urllib2.urlopen('http://nemo.isis.unc.edu/data/nccoos/level0/dukeforest/sodar/store/2007-06/20070601.dat') 
     211        dataString = dataHandle.read() 
    119212    except: 
    120         print "Failure to read test data" 
    121     data = Data(data
    122     print data['2007-06-01-09-15']['70']['SPEED'] 
     213        raise IOError("Failure to read test data") 
     214    dataObject = Data(dataString
     215    print dataObject['2007-06-01-09-15']['70']['SPEED'] 
    123216 
    124217if __name__ == "__main__": 
    125     __main() 
     218    _main()