source: nappy/branches/nappy-eggable/nappy/nc_interface/na_to_cdms.py @ 5138

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/nappy/branches/nappy-eggable/nappy/nc_interface/na_to_cdms.py@5138
Revision 5138, 16.1 KB checked in by spascoe, 11 years ago (diff)

Merging changes in the nappy package from trunk

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"""
6na_to_cdms.py
7=============
8
9Container module for class NADictToCdmsObjects that is sub-classed
10by NAToNC classes.
11
12"""
13
14# Imports from python standard library
15import sys
16import re
17import time
18
19# Import from nappy package
20from nappy.na_error import na_error
21import nappy.utils
22import nappy.utils.common_utils
23
24config_dict = nappy.utils.getConfigDict()
25na_to_nc_map = config_dict["na_to_nc_map"]
26header_partitions = config_dict["header_partitions"]
27hp = header_partitions
28
29# Import external packages (if available)
30if sys.platform.find("win") > -1:
31    raise na_error.NAPlatformError("Windows does not support CDMS. CDMS is required to convert to CDMS objects and NetCDF.")
32
33try:
34    import cdms2 as cdms
35    import numpy as N
36except:
37    try:
38        import cdms
39        import Numeric as N
40    except:
41        raise Exception("Could not import third-party software. Nappy requires the CDMS and Numeric packages to be installed to convert to CDMS and NetCDF.")
42
43
44# Define global variables
45safe_nc_id = re.compile("[\/\s\[\(\)\]\=\+\-\?\#\~\@\&\$\%\!\*\{\}\^]+")
46time_units_pattn = re.compile("\w+\s+since\s+\d{4}-\d{1,2}-\d{1,2}\s+\d+:\d+:\d+")
47max_id_length = 40
48special_comment_known_strings = (hp["sc_start"], hp["sc_end"], hp["addl_vatts"],
49                                  hp["addl_globals"], "\n")
50
51normal_comment_known_strings = (hp["nc_start"], hp["nc_end"], hp["data_next"], 
52                                  hp["addl_vatts"], hp["addl_globals"], "\n")
53
54time_units_warning_message = """\nWARNING: Could not recognise time units. For true NetCDF compability
55please insert the correct time unit string below in the format:
56   
57    <units> since <YYYY>-<MM>-<DD> <hh>-<mm>-<ss>
58   
59Where:
60    <units> is a known time interval such as years, months, days, etc.
61    <YYYY> is the year, <MM> is the month, <DD> is the day,
62    <hh> is the hour, <mm> is minutes, <ss> is seconds.
63"""
64
65DEBUG = nappy.utils.getDebug() 
66
67
68class NADictToCdmsObjects:
69    """
70    Converts a NA File instance to a tuple of CDMS objects.
71    """
72   
73    def __init__(self, na_file_obj, variables="all", aux_variables="all",
74                 global_attributes=[("Conventions", "CF-1.0")],
75                 time_units=None, time_warning=True, 
76                 rename_variables={}):
77        """
78        Sets up instance variables.
79        """
80        self.na_file_obj = na_file_obj     
81        self.variables = variables
82        self.aux_variables = aux_variables
83        self.global_attributes = global_attributes
84        self.time_units = time_units
85        self.time_warning = time_warning
86        self.rename_variables = rename_variables
87
88        # Check if we have capability to convert this FFI
89        if self.na_file_obj.FFI in (2110, 2160, 2310): 
90                raise Exception("Cannot convert NASA Ames File Format Index (FFI) " + `self.na_file_obj.FFI` + " to CDMS objects. No mapping implemented yet.")
91
92        self.output_message = []  # for output printing message
93        self.converted = False
94
95    def convert(self):
96        """
97        Reads the NASA Ames file object and converts to CDMS objects.
98        Returns (variable_list, aux_variable_list, global_attributes_list).
99        All these can be readily written to a CDMS File object.
100        """
101        if self.converted == True:
102            print "Already converted to CDMS objects."
103            return (self.cdms_variables, self.cdms_aux_variables, self.global_attributes)
104
105        self.na_file_obj.readData()
106
107        # Convert global attribute
108        self._mapNACommentsToGlobalAttributes()
109
110        # Convert axes
111        if not hasattr(self, 'cdms_axes'):  self._convertCdmsAxes()
112
113        # Convert main variables
114        if not hasattr(self, 'cdms_variables'):  self._convertCdmsVariables()
115
116        # Then do auxiliary variables
117        if hasattr(self.na_file_obj, "NAUXV") and (type(self.na_file_obj.NAUXV) == type(1) and self.na_file_obj.NAUXV > 0):   # Are there any auxiliary variables?
118            if not hasattr(self, 'cdms_aux_variables'): 
119                self._convertCdmsAuxVariables()
120        else:
121            self.cdms_aux_variables = []
122           
123        self.converted = True
124        return (self.cdms_variables, self.cdms_aux_variables, self.global_attributes)
125
126
127    def _mapNACommentsToGlobalAttributes(self):
128        """
129        Maps the NASA Ames comments section to global attributes and append them to the
130        self.global_attributes list.
131        """
132        glob_atts = dict(self.global_attributes)
133       
134        for key in na_to_nc_map.keys():
135
136            if type(key) == type((1,2)):
137
138                if key == ("SCOM", "NCOM"):
139                    # Map special and normal comments into the global comments section
140                    comment_line = ""
141
142                    # Add Special comments first
143                    if self.na_file_obj.NSCOML > 0:
144                        comment_line += (hp["sc_start"] + "\n")
145
146                        for i in self.na_file_obj.SCOM: 
147                           
148                            if i.strip() not in special_comment_known_strings:
149                                comment_line += ("\n" + i)
150                           
151                        comment_line += ("\n" + hp["sc_end"] + "\n")
152
153                    # Now add normal comments
154                    if self.na_file_obj.NNCOML > 0:
155                        comment_line += (hp["nc_start"] + "\n")
156
157                        for i in self.na_file_obj.NCOM: 
158                            if i.strip() not in normal_comment_known_strings:
159                                comment_line += ("\n" + i)
160
161                        comment_line += ("\n" + hp["nc_end"])
162
163                    # Tidy comment line then write to global atts dict
164                    comment_line = comment_line.replace("\n\n", "\n")
165
166                    #self.global_attributes["comment"] = comment_line
167                    glob_atts["comment"] = comment_line
168
169                elif key == ("ONAME", "ORG"):
170                    # Map the two organisation NA files to the institution field in CDMS (NetCDF)
171                    institution = "%s (ONAME from NASA Ames file); %s (ORG from NASA Ames file)." % \
172                                         (self.na_file_obj.ONAME, self.na_file_obj.ORG)
173                    #self.global_attributes["institution"] = institution
174                    glob_atts["institution"] = institution
175                    #self.cdms_file.institution = "%s (ONAME from NASA Ames file); %s (ORG from NASA Ames file)." % (self.na_file_obj.ONAME, self.na_file_obj.ORG)
176
177                else:
178                    # Any other strange tuple just gets merged into a string
179                    item = (getattr(self.na_file_obj, key[0])) + "\n" + (getattr(self.na_file_obj, key[1]))
180
181                    # setattr(self.cdms_file, cdms_map.fromNA[key], item)
182                    #self.global_attributes[na_to_nc_map[key]] = item
183                    glob_atts[na_to_nc_map[key]] = item
184
185            elif key == "RDATE":
186                # RDATE = Revision date - update this and put in history global attribute
187                date_parts = getattr(self.na_file_obj, "RDATE")
188                date_string = "%.4d-%.2d-%.2d" % tuple(date_parts)
189                hist = date_string + " - NASA Ames File created/revised.\n"
190                time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
191                version = nappy.utils.getVersion()
192                hist = "%s\n%s - Converted to CDMS (NetCDF) format using nappy-%s." % (hist, time_string, version)
193                # self.cdms_file.history = hist
194                print "DIDNT HAVE MAPPING SO PUT HISTORY IN LINE 160 IN na_to_cdms.py"
195                #self.global_attributes["history"] = hist
196                glob_atts["history"] = hist           
197            else:
198                # Anything else just needs to be stored as a global attribute
199                # setattr(self.cdms_file, cdms_map.fromNA[key], getattr(self, key))
200                #self.global_attributes[na_to_nc_map[key]] = getattr(self.na_file_obj, key)
201                glob_atts[na_to_nc_map[key]] = getattr(self.na_file_obj, key)
202
203        # Now remake global atts list
204        new_atts = []
205
206        for key, value in self.global_attributes:
207            new_atts.append( (key, glob_atts[key]) )
208        used_keys = [i[0] for i in new_atts]
209
210        for key in glob_atts.keys():
211            if key not in used_keys:
212                new_atts.append( (key, glob_atts[key]) )
213
214        self.global_attributes = new_atts[:]
215
216    def _convertCdmsVariables(self):
217        """
218        Creates cdms variable list for writing out.
219        """
220        self.cdms_variables = []
221
222        if self.variables in (None, "all"):   
223            for var_number in range(self.na_file_obj.NV):
224                self.cdms_variables.append(self._convertNAToCdmsVariable(var_number))
225        else:
226            if type(self.variables[0]) == type(1) or re.match("\d+", str(self.variables[0])): # They are integers = indices
227                for var_number in self.variables:
228                    vn = int(var_number)
229                    self.cdms_variables.append(self._convertNAToCdmsVariable(vn))   
230            elif type(self.variables[0]) == type("string"):  # Vars are strings
231                for var_name in self.variables:
232                    if var_name in self.na_file_obj.VNAME:
233                        var_number = self.na_file_obj.VNAME.index(var_name)
234                        self.cdms_variables.append(self._convertNAToCdmsVariable(var_number))
235                    else:
236                        raise Exception("Variable name not known: " + var_name)
237
238
239    def _convertNAToCdmsVariable(self, var_number, attributes={}):
240        """
241        Creates a single cdms variable from the variable number provided in the list.
242        """
243        (var_name, units, miss, scal) = self.na_file_obj.getVariable(var_number)
244        msg = "\nAdding variable: %s" % self.na_file_obj.VNAME[var_number]
245        print msg
246        self.output_message.append(msg)
247        array = N.array(self.na_file_obj.V[var_number])
248        array = array * scal
249        # Set up axes
250        if not hasattr(self, 'cdms_axes'):
251            self._convertCdmsAxes()
252
253        # Set up variable
254        var=cdms.createVariable(array, axes=self.cdms_axes, fill_value=miss, attributes=attributes)
255
256        # Sort units etc
257        if units:   var.units=units
258       
259        # Add the best variable name
260        if len(var_name) < max_id_length:
261            var.id=safe_nc_id.sub("_", var_name).lower()
262        else:
263            var.id="naVariable_%s" % (var_number)
264       
265             # Check if mapping provided for renaming this variable
266        if var_name in self.rename_variables.keys():
267            var_name = self.rename_variables[var_name]
268           
269        var.long_name = var.name = var.title = var_name
270
271        # Add a NASA Ames variable number (for mapping correctly back to NASA Ames)
272        var.nasa_ames_var_number = var_number
273        return var
274
275    def _convertCdmsAuxVariables(self):
276        """
277        Creates a cdms variable from an auxiliary variable
278        """
279        self.cdms_aux_variables = []
280
281        if self.aux_variables in (None, "all"):
282            for avar_number in range(self.na_file_obj.NAUXV):
283                self.cdms_aux_variables.append(self._convertNAAuxToCdmsVariable(avar_number))
284        else:
285            if type(self.aux_variables[0]) == type(1): # They are integers = indices
286                for avar_number in self.aux_variables:
287                    self.cdms_aux_variables.append(self._convertNAAuxToCdmsVariable(avar_number))   
288
289            elif type(self.aux_variables[0]) == type("string"): # They are strings
290                for avar_name in self.aux_variables:
291                    if avar_name in self.na_file_obj.ANAME:
292                        avar_number = self.na_file_obj.ANAME.index(avar_name)
293                        self.cdms_aux_variables.append(self._convertNAAuxToCdmsVariable(avar_number)) 
294            else:
295                raise Exception("Auxiliary variable name not known: " + avar_name)         
296
297    def _convertNAAuxToCdmsVariable(self, avar_number, attributes={}):
298        """
299        Converts an auxiliary variable to a cdms variable.
300        """
301        (var_name, units, miss, scal) = self.na_file_obj.getAuxVariable(avar_number)
302        array = N.array(self.na_file_obj.A[avar_number])
303        array = array * scal
304
305        msg="\nAdding auxiliary variable: %s" % self.na_file_obj.ANAME[avar_number]
306        print msg
307        self.output_message.append(msg)
308
309        # Set up axes
310        if not hasattr(self, 'cdms_axes'):
311            self._convertCdmsAxes()
312
313        # Set up variable
314        var = cdms.createVariable(array, axes=[self.cdms_axes[0]], fill_value=miss, 
315                                  attributes=attributes)
316
317        # Sort units etc
318        if units:   var.units = units
319        if len(var_name) < max_id_length:
320            var.id = safe_nc_id.sub("_", var_name).lower()
321        else:
322            var.id = "naAuxVariable_%s" % (avar_number)
323
324            # Check if mapping provided for renaming this variable
325        if var_name in self.rename_variables.keys():
326            var_name = self.rename_variables[var_name]
327
328        var.long_name = var.name = var.title = var_name
329
330        # Add a NASA Ames auxiliary variable number (for mapping correctly back to NASA Ames)
331        var.nasa_ames_aux_var_number = avar_number
332        return var       
333
334    def _convertCdmsAxes(self): 
335        """
336        Creates cdms axes from information provided in the NASA Ames dictionary.
337        """
338        if not hasattr(self, 'cdms_axes'):       
339            self.cdms_axes = []
340
341        for ivar_number in range(self.na_file_obj.NIV):
342            self.cdms_axes.append(self._convertNAIndVarToCdmsAxis(ivar_number))
343
344
345    def _convertNAIndVarToCdmsAxis(self, ivar_number):
346        """
347        Creates a cdms axis from a NASA Ames independent variable.
348        """
349        if self.na_file_obj._normalized_X == False:   self.na_file_obj._normalizeIndVars()
350
351        if self.na_file_obj.NIV == 1:
352            array = self.na_file_obj.X
353        else:
354            array = self.na_file_obj.X[ivar_number]
355
356        axis = cdms.createAxis(array)
357        axis.id = axis.name = axis.long_name = self.na_file_obj.XNAME[ivar_number]
358        (var_name, units) = self.na_file_obj.getIndependentVariable(ivar_number)
359       
360        # Sort units etc
361        if units:   axis.units = units
362        if len(var_name) < max_id_length:
363            axis.id = safe_nc_id.sub("_", var_name).lower()
364        else:
365            axis.id = "naIndVariable_%s" % (ivar_number)
366
367        if units: axis.units = units
368
369        axis_types = ("longitude", "latitude", "level", "time")
370
371        for axis_type in axis_types:
372            if re.search(axis_type, var_name, re.IGNORECASE):
373                axis.standard_name = axis.id = axis_type
374                # Designate it CF-style if known axis type (e.g. axis.designateTime() etc..)
375                exec "axis.designate%s()" % axis_type.title()
376                print "CHECK AXIS GOT DESIGNATED PROPERLY - RE-FACTORED" * 5
377
378        # Check warning for time units pattern
379        if axis.isTime() and (not hasattr(axis, "units") or not time_units_pattn.match(axis.units)):
380            if self.time_units == None:
381                time_units_input = "I WON'T MATCH"
382
383                while time_units_input != "" and not time_units_pattn.match(time_units_input):
384                    message = time_units_warning_message                           
385                    if self.time_warning == True:
386                        print message
387                        time_units_input = raw_input("Please insert your time unit string here (or leave blank):").strip()
388                    else: 
389                        time_units_input = ""
390
391                self.output_message.append(message)
392                self.time_units = time_units_input
393
394            axis.units = self.time_units
395            axis.long_name = axis.name = "time (%s)" % self.time_units
396
397        if not hasattr(axis, "units") or axis.units == None: 
398            if units:
399                axis.units = units     
400            else:
401                axis.units = "Not known"
402
403        return axis
Note: See TracBrowser for help on using the repository browser.