source: TI03-DataExtractor/trunk/pydxs/CDMSDataHandler.py @ 794

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI03-DataExtractor/trunk/pydxs/CDMSDataHandler.py@794
Revision 794, 17.8 KB checked in by astephen, 13 years ago (diff)

Unstable but latest version with multi-variable support and split hooks
for CDML and CSML.

Line 
1#   Copyright (C) 2004 CCLRC & NERC( Natural Environment Research Council ).
2#   This software may be distributed under the terms of the
3#   Q Public License, version 1.0 or later. http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt
4
5"""
6CDMSDataHandler.py
7==================
8
9CDMSDataHandler module for the dx package.
10
11This module holds the CDMSDataHandler class that is used
12to hold and access information about datasets held in CDMS-type
13formats visible to the dx package.
14
15"""
16
17# Import required modules
18import shelve
19import os
20import cdms
21import re
22
23# Import global variables
24from serverConfig import *
25from common import *
26from DXDMLHandler import DXDMLHandler
27from DXErrors import *
28
29
30class CDMSDataHandler:
31    """
32    A group of methods to connect to a dataset group or
33    dataset to extract information about the contents.
34    """
35 
36    def __init__(self, datasetURI=None):
37        """
38        Set up instance variables.
39        """
40        self.DXDML=DXDMLHandler()
41        self.file=datasetURI
42        if self.file: self._openDataFile(datasetURI=self.file)
43   
44   
45    def _openDataFile(self, datasetGroup=None, dataset=None, datasetURI=None):
46        """
47        Opens a file and allocates to file handle called: self.file.
48        """
49        if datasetURI:
50            cdmlfile=datasetURI
51        else:
52            for item in self.DXDML.getDatasetsAndDatasetURIs(datasetGroup):
53                if item[0]==dataset:
54                    cdmlfile=item[1]
55       
56        try:
57            self.file=cdms.open(cdmlfile)
58        except IOError, error:
59            raise DXDataIOError, error
60
61
62    def _getVariable(self, varname):
63        """
64        Gets variable metadata object from a data file.
65        """
66        try:
67            rtvalue=self.file[varname]
68        except:
69            raise DXOptionHandlingError, "Cannot find variable %s in file %s" % (varname, self.file.id)
70        return rtvalue
71       
72
73    def _getBestName(self, v, vcount=0):
74        """
75        Returns the best name for a cdms variable.
76        """
77        if not hasattr(v, "standard_name"): 
78            if not hasattr(v, "long_name"):
79                if not hasattr(v, "title"):
80                    if not hasattr(v, "name"):
81                        if hasattr(v, "id"):
82                            name=v.id
83                        else:
84                            vcount=vcount+1
85                            name="unnamed_var_%s" % vcount
86                    else:
87                       name=v.name
88                else:
89                    name=v.title
90            else:
91                name=v.long_name
92        else:
93            name=v.standard_name
94        return name
95
96
97    def getVariables(self, datasetGroup=None, dataset=None, datasetURI=None):
98        """
99        Returns a list of variables for the given dataset
100        group/dataset combination or datasetURI. The variable name used is selected
101        hierarchically depending on the available attributes. Each returned item in
102        the list includes a [<long_name>, <id>].
103        """ 
104        self._openDataFile(datasetGroup, dataset, datasetURI)
105        vars=self.file.listvariables()
106        rtvars=[]
107        vcount=0
108        for var in vars:
109           v=self.file[var]
110           name=self._getBestName(v, vcount)
111           """if not hasattr(v, "standard_name"):
112               if not hasattr(v, "long_name"):
113                   if not hasattr(v, "title"):
114                       if not hasattr(v, "name"):
115                           if hasattr(v, "id"):
116                               name=v.id
117                           else:
118                               vcount=vcount+1
119                               name="unnamed_var_%s" % vcount
120                       else:
121                           name=v.name
122                   else:
123                       name=v.title
124               else:
125                   name=v.long_name
126           else:
127               name=v.standard_name"""
128               
129           # Fix name to remove leading asterisks and lower case surface start.
130           name=name.replace("_", " ")
131           if name[:2]=="**": name=(name[2:]).strip()
132           if name[:7]=="surface": name=(name[7:]).strip()
133           # Remove variables they are actually bounds on axes or coefficients in formulae
134           if v.id not in ("bounds_longitude", "bounds_latitude", "bounds_level", "bounds_time", "p0"):
135               rtvars.append([name, v.id]) 
136
137        return rtvars
138
139
140    def getDomain(self, datasetGroup=None, dataset=None, variable=None, datasetURI=None):
141        """
142        Returns the full domain listing for a variable returning:
143       
144        [axisIndexString, knownAxisString, id, longName, units, listType, unusedItem,
145        listValue-1, listValue-2, ..., listValue-n]
146       
147        For example:
148       
149        ["axis1.0", "time", "time", "Time", "hours since 1999-09-09 00:00:00", "start end interval",
150        "", 0, 3, 6]
151       
152        This listType represents 6-hourly time steps of 0,1,2,3 past the base time.
153       
154        listType can also take the value "full list" where all values in the list are provided,
155        or "start end" where only the first and last value are given.
156        """ 
157        self._openDataFile(datasetGroup, dataset, datasetURI)
158        var=self._getVariable(variable)
159        varList=self.file.listvariables()
160        varList.sort()
161        varIndex=varList.index(var.id)
162        rtlist=[]
163       
164        axcount=0
165        for axis in var.getAxisList():
166            axisIndexString="axis%s.%s" % ("77777", axcount) #(varIndex+1, axcount)
167            units=None
168            if axis.isTime():
169                knownAxis="time"
170                (start, end, (intervalValue, intervalUnits))=self.getTemporalDomain(datasetGroup, dataset, variable, datasetURI)
171                arrayValues=[start, end, intervalValue]
172                listType="start end interval"
173                units=intervalUnits
174            elif axis.isLevel():
175                knownAxis="level"
176                arrayValues=axis[:]
177                listType="full list"
178            elif axis.isLatitude():
179                knownAxis="latitude"
180                arrayValues=[axis[0], axis[-1]]
181                arrayValues.sort()
182                arrayValues.reverse()
183                listType="start end" 
184            elif axis.isLongitude():
185                knownAxis="longitude"
186                arrayValues=[axis[0], axis[-1]]
187                arrayValues.sort()     
188                listType="start end"                                   
189            else:
190                # For any axis not known as above
191                knownAxis=""
192                if len(axis[:])>200:
193                    arrayValues=[axis[0], axis[-1]]
194                    listType="start end"                   
195                else:
196                    arrayValues=axis[:]
197                    listType="full list"
198           
199            id=axis.id
200            longName=self._getBestName(axis).title()
201            if not units:  units=getattr(axis, "units", "")
202           
203            unused=""
204            rtlist.append([axisIndexString, knownAxis, id, longName, units, listType, unused]+arrayValues)
205            axcount=axcount+1
206           
207        return rtlist
208       
209
210    def getHorizontalDomain(self, datasetGroup=None, dataset=None, variable=None, datasetURI=None):
211        """
212        Returns the horizontal domain as (northernExtent, westernExtent, southernExtent, easternExtent).
213        """
214        self._openDataFile(datasetGroup, dataset, datasetURI)
215        var=self._getVariable(variable)
216        lat=list(var.getLatitude()[:])
217        if lat[-1]<lat[0]: lat.reverse()
218        (southernExtent, northernExtent)=(lat[0], lat[-1])
219        lon=var.getLongitude()[:]
220        (westernExtent, easternExtent)=(lon[0], lon[-1])
221        return (northernExtent, westernExtent, southernExtent, easternExtent)
222
223
224    def getVerticalSpatialDomain(self, datasetGroup=None, dataset=None, variable=None, datasetURI=None):
225        """
226        Returns the vertical domain as a tuple containing
227        a list of levels (or "Single level" string) and the units.
228        """
229        self._openDataFile(datasetGroup, dataset, datasetURI)
230        var=self._getVariable(variable)
231        try:
232            levels=var.getLevel()
233            vertical_units=levels.units
234            vertical_domain=tuple(map(lambda x: float(x), levels[:]))
235     
236        except:
237            vertical_domain=("Single level",)
238            vertical_units=None
239        return (vertical_domain, vertical_units)
240
241
242    def getTemporalDomain(self, datasetGroup=None, dataset=None, variable=None, datasetURI=None):
243        """
244        Returns the temporal domain as a tuple of (start time, end time,
245        (interval value, interval units)).
246        """
247        self._openDataFile(datasetGroup, dataset, datasetURI)
248        var=self._getVariable(variable)
249        time_axis=var.getTime()
250        time_keys=("year", "month", "day", "hour", "minute", "second")
251        start_time_components=time_axis.asComponentTime()[0]
252        end_time_components=time_axis.asComponentTime()[-1]
253        start_time=[]
254        end_time=[]
255        for key in time_keys:
256            start_time.append(getattr(start_time_components, key))
257            end_time.append(getattr(end_time_components, key)) 
258        time_units=time_axis.units.split()[0]
259        if time_units[-1]=="s":  time_units=time_units[:-1]
260        if len(time_axis)>1:
261            interval_value=abs(time_axis[1]-time_axis[0])
262        else:
263            interval_value=1  # To stop loops breaking later!
264        return (start_time, end_time, (interval_value, time_units)) 
265
266
267    def getSelectedTimeSteps(self, datasetURI, variable, axisSelectionDict):
268        """
269        Returns a list of time step strings based on the selection.
270        """     
271        self._openDataFile(datasetURI=datasetURI)
272        var=self._getVariable(variable)
273       
274        timeAxis=None
275        axcount=0
276        for axis in var.getAxisList():
277            if axis.isTime():
278                timeAxisIndex=axcount
279                timeAxis=axis
280            axcount=axcount+1
281
282        if timeAxis==None:
283            return []
284         
285        startDateTime=None   
286        for key in axisSelectionDict.keys():
287            #print key, axisSelectionDict[key]
288            axisIndex=int(key.split(".")[-1])
289            if axisIndex==timeAxisIndex:
290                (startDateTime, endDateTime)=axisSelectionDict[key][:2]
291       
292        if startDateTime==None:
293            return [str(tst) for tst in timeAxis.asComponentTime()]
294       
295        startDateTime=startDateTime.replace("T", " ")
296        items=startDateTime.split(":")
297        startDateTime=":".join(items[:-1])+":"+("%f" % float(items[-1]))
298        endDateTime=endDateTime.replace("T", " ")
299        items=endDateTime.split(":")
300        endDateTime=":".join(items[:-1])+":"+("%f" % float(items[-1]))
301       
302        timeSteps=timeAxis.asComponentTime()
303        selectedTimes=[]
304
305        for timeStep in timeSteps:
306            ts=timeStep
307            timeStep="%.4d-%.2d-%.2d %.2d:%.2d:%f" % (ts.year, ts.month, ts.day, \
308                                            ts.hour, ts.minute, ts.second)
309            #print str(timeStep), startDateTime
310            ts=str(timeStep)
311            if ts>endDateTime:
312                break
313            elif ts<startDateTime:
314                continue
315            else:
316                selectedTimes.append(ts)
317       
318        if selectedTimes==[]:
319            raise DXOptionHandlingError, "All selected time steps for '%s' are out of range, please go back and re-select." % variable
320               
321        return selectedTimes
322
323
324    """def getSelectedVariableSubsetSize(self, datasetURI, variable, axisSelectionDict):
325        "
326        Returns the size in bytes of the selected subset of a variable.
327        "       
328        self._openDataFile(datasetURI=datasetURI)
329        var=self._getVariable(variable)
330        varType=var.typecode()
331       
332        timeAxisIndex=None
333        axcount=0
334        for axis in var.getAxisList():
335            if axis.isTime():
336                timeAxisIndex=axcount
337            axcount=axcount+1
338         
339        startDateTime=None   
340       
341        axesCounted=[]
342        size=1
343        for key in axisSelectionDict.keys():
344            #print key, axisSelectionDict[key]
345            axisIndex=int(key.split(".")[-1])
346            (low, high)=axisSelectionDict[key][:2]
347            axis=var.getAxis(axisIndex)
348           
349            if axisIndex==timeAxisIndex:
350                axlen=len(self.getSelectedTimeSteps(datasetURI, variable, {key:(low, high)}))           
351            elif low==high:
352                axlen=1
353            else:
354                axlen=len(getValuesInRange(low, high, axis[:]))
355                if axlen==0:
356                    raise DXOptionHandlingError, "All selected '%s' axis values for '%s' are out of range, please go back and re-select." % (axis.id, variable)
357            size=size*axlen
358            axesCounted.append(axisIndex)
359       
360        axcount=0   
361        for axis in var.getAxisList():
362            if axcount not in axesCounted:
363                size=size*len(axis)
364           
365            axcount=axcount+1
366
367        if varType=="f":
368            size=size*4.
369        elif varType=="d":
370            size=size*8
371        elif varType=="i":
372            size=size   
373
374        return size     """
375
376
377    def getSelectedVariableArrayDetails(self, datasetURI, variable, axisSelectionDict):
378        """
379        Returns a tuple representing the (array shape, grid shape, size)
380        of the selected subset of a variable. Grid shape can be None if both latitude
381        and longitude axes are not present.
382        """     
383        self._openDataFile(datasetURI=datasetURI)
384        var=self._getVariable(variable)
385        varType=var.typecode()
386               
387        timeAxisIndex=None
388        axcount=0
389        for axis in var.getAxisList():
390            if axis.isTime():
391                timeAxisIndex=axcount
392            axcount=axcount+1
393         
394        startDateTime=None   
395       
396        axesCounted=[]
397        axLens=[]
398        latLength=None
399        lonLength=None
400        size=1
401        for key in axisSelectionDict.keys():
402            #print key, axisSelectionDict[key]
403            axisIndex=int(key.split(".")[-1])
404            (low, high)=axisSelectionDict[key][:2]
405            axis=var.getAxis(axisIndex)
406           
407            if axisIndex==timeAxisIndex:
408                axlen=len(self.getSelectedTimeSteps(datasetURI, variable, {key:(low, high)}))           
409            elif low==high: 
410                axlen=1
411            else:
412                axlen=len(getValuesInRange(low, high, axis[:]))
413                if axlen==0:
414                    if (axis.isLongitude() or axis.isLatitude()) and low==high:
415                        print "Lat and lon can be axis length zero because we'll nudge to nearest if only one value given."
416                        axlen=1
417                    else:
418                        raise DXOptionHandlingError, "All selected '%s' axis values for '%s' are out of range, please go back and re-select." % (axis.id, variable)
419       
420                if axis.isLatitude():
421                    latLength=axlen
422                elif axis.isLongitude():
423                    lonLength=axlen
424                       
425            size=size*axlen
426            axesCounted.append(axisIndex)
427            axLens.append(axlen)
428       
429        axcount=0
430        arrayShape=[]
431        for axis in var.getAxisList():
432            if axcount not in axesCounted:
433                size=size*len(axis)
434                arrayShape.append(len(axis))
435            else:
436                arrayShape.append(axLens[axesCounted[axcount]]) 
437            if axis.isLatitude() and latLength==None:
438                latLength=len(axis)
439            elif axis.isLongitude() and lonLength==None:
440                lonLength=len(axis)   
441            axcount=axcount+1
442       
443        # Now work out gridShape if appropriate
444        if latLength and lonLength:
445            gridShape=(latLength, lonLength)
446        else:
447            gridShape=None
448       
449        if varType=="f":
450            size=size*4.
451        elif varType=="d":
452            size=size*8
453        elif varType=="i":
454            size=size   
455
456        return (tuple(arrayShape), gridShape, size)
457
458
459    def getSelectedVariableSubsetSize(self, datasetURI, varID, axisSelectionDict):
460        """
461        Returns the size in bytes of the selected subset of a variable.
462        """
463        return self.getSelectedVariableArrayDetails(datasetURI, varID, axisSelectionDict)[2]
464
465       
466    def readVariableSubsetIntoMemory(self, datasetURI, variable, axisSelectionDict, timeStep=None):
467        """
468        Reads the variable with ID 'variable' into memory from file
469        'datasetURI' - sub-setting across all axes indicated in 'axisSelectionDict'.
470        If 'timeStep' is provided then override the time selection in 'axisSelectionDict'
471        with the 'timeStep' given.
472        """
473        self._openDataFile(datasetURI=datasetURI)
474        var=self._getVariable(variable)
475       
476        axisList=var.getAxisList()
477           
478        selectionStrings=[]
479        for key in axisSelectionDict.keys():
480            #print key, axisSelectionDict[key]
481            axisIndex=int(key.split(".")[-1])
482            axis=axisList[axisIndex]   
483            id=axis.id
484           
485            # deal with time differently
486            if axis.isTime():
487                if timeStep!=None:     
488                    timeStep=timeStep.replace("T", " ")                 
489                    selectionStrings.append('time="%s"' % timeStep)
490                else:
491                    selector=axisSelectionDict[key][:2]
492                    selector=(selector[0].replace("T", " "), selector[1].replace("T", " "))
493                    selectionStrings.append('%s=%s' % (axis.id, str(selector)))         
494            elif axis.isLatitude():
495                (low, high)=axisSelectionDict[key][:2]
496                print "Nudging latitudes to nearest points..."
497                if low==high:
498                    (low, high, nudgeMessage)=nudgeSingleValuesToAxisValues(low, axis[:], "Latitude")       
499            elif axis.isLongitude():
500                (low, high)=axisSelectionDict[key][:2]
501                print "Nudging longitudes to nearest points..."
502                if low==high:
503                    (low, high, nudgeMessage)=nudgeSingleValuesToAxisValues(low, axis[:], "Longitude")   
504            else:
505                selector=axisSelectionDict[key][:2]
506                selectionStrings.append('%s=%s' % (axis.id, str(selector)))
507       
508        fullSelectionString=", ".join(selectionStrings)
509        variableData=eval("self.file('%s', %s)" % (variable, fullSelectionString))
510        return variableData         
511       
512
513    def getCFGlobalAttributes(self, datafile):
514        """
515        Gets any CF metadta global attributes that are available
516        from the source dataset/file.
517        """
518        # Make sure data file is open
519        if self.file==None: self._openDataFile(datasetURI=datafile)
520        gatts={}
521
522        for gatt in CF_METADATA_GLOBAL_ATTRIBUTE_KEYS:
523            if hasattr(self.file, gatt):
524                gatts[gatt]=self.file.__getattr__(gatt)
525       
526        return gatts
527
528
529if __name__=="__main__":       
530    a=CDMSDataHandler()
531    print a.getVariables(datasetGroup='Test Data Group 1', dataset='Test Dataset 1')
532    print a.getVariables(datasetGroup='Test Data Group 3', datasetURI='file:/usr/local/dx/testdata/testdata3.xml')   
533    print a.getDomain('Test Data Group 3', 'Test Dataset 3', "var3")
534    print a.getHorizontalDomain('Test Data Group 3', 'Test Dataset 3', "var3")
535    print a.getVerticalSpatialDomain('Test Data Group 3', 'Test Dataset 3', "var3")
536    print a.getTemporalDomain('Test Data Group 3', 'Test Dataset 3', "var3")
537    print a.getCFGlobalAttributes("file:/usr/local/dx/testdata/testdata3.xml")
538    print a.getSelectedVariableArrayDetails('file:/usr/local/dx/testdata/testdata1.xml', "pqn", 
539                     {"axis_1.1.1.0":("1999-01-01T00:00:00", "1999-01-01T06:00:00"),
540                     "axis_1.1.1.1":(30,-30), "axis_1.1.1.2":(30,-30)})
Note: See TracBrowser for help on using the repository browser.