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

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

Stable-ish version with fully-ish working dxc client.

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