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

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