source: nappy/trunk/nappy/nc_interface/na_content_collector.py @ 5905

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/nappy/trunk/nappy/nc_interface/na_content_collector.py@5905
Revision 5905, 30.3 KB checked in by astephen, 11 years ago (diff)

Removed dependency on local cdms_utils, now using official egg.

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_content_collector.py
7=======================
8
9Holds the class NAContentCollector that converts a set of CDMS variables and global attributes to a NASA Ames dictionary.
10
11"""
12
13# Imports from python standard library
14import sys
15import time
16import re
17import logging
18
19# Import from nappy package
20from nappy.na_error import na_error
21import nappy.utils
22import cdms_utils.axis_utils
23import cdms_utils.var_utils
24import nappy.utils.common_utils
25import nappy.na_file.na_core
26
27config_dict = nappy.utils.getConfigDict()
28nc_to_na_map = config_dict["nc_to_na_map"]
29header_partitions = config_dict["header_partitions"]
30hp = header_partitions
31
32version = nappy.utils.getVersion()
33
34# Import external packages (if available)
35if sys.platform.find("win") > -1:
36    raise na_error.NAPlatformError("Windows does not support CDMS. CDMS is required to convert to CDMS objects and NetCDF.")
37try:
38    import cdms2 as cdms
39    import numpy as N
40except:
41    try:
42        import cdms
43        import Numeric as N
44    except:
45        raise Exception("Could not import third-party software. Nappy requires the CDMS and Numeric packages to be installed to convert to CDMS and NetCDF.")
46
47cdms.setAutoBounds("off") 
48DEBUG = nappy.utils.getDebug() 
49
50logging.basicConfig()
51log = logging.getLogger(__name__)
52
53class NAContentCollector(nappy.na_file.na_core.NACore):
54    """
55    Class to build a NASA Ames File object from a set of
56    CDMS variables and global attributes (optional).
57    """
58   
59    def __init__(self, variables, global_attributes=[], requested_ffi=None):
60        """
61        Sets up instance variables and calls appropriate methods to
62        generate sections of NASA Ames file object.
63
64        Input arguments are:
65          * variables - list/tuple of actual CDMS variables
66          * global_attributes - list of user-defined global (key,value) attributes to include.
67
68        Typical usage:
69        >>> x = NAContentCollector(["temp", "precip"])
70        >>> x.collectNAContent()
71        >>> if x.found_na == True:
72        ...     print x.na_dict, x.var_ids, x.unused_vars
73        """
74        self.output_message = []
75        self.na_dict = {}
76        self.vars = variables
77
78        # Note that self.var_ids will be a list containing:
79        #    [ordered_vars,  auxiliary_vars,   rank_zero_vars]
80        self.var_ids = None
81        self.globals = dict(global_attributes)
82        self.requested_ffi = requested_ffi
83
84        self.rank_zero_vars = []
85        self.rank_zero_var_ids = []
86
87        # Create a flag to check if anything found
88        self.found_na = False
89
90
91    def collectNAContent(self):
92        """
93        Collect NASA Ames content. Save the contents to the following instance
94        attributes:
95         * self.na_dict
96         * self.var_ids
97         * self.unused_vars
98        """
99        log.debug("Call to collectNAContent():\n")
100        for v in self.vars: 
101            log.debug("\t%s, %s, %s" % (v.id, v.shape, v.getAxisIds()))
102 
103        (self.ordered_vars, aux_vars) = self._analyseVariables()
104     
105        if self.ordered_vars == []:
106            log.warn("No NASA Ames content created.")
107            self.unused_vars = []
108        else:
109            self.var_ids = [[var.id for var in self.ordered_vars],
110                            [var.id for var in aux_vars], 
111                            self.rank_zero_var_ids]
112            self.na_dict["NLHEAD"] = -999
113            self._defineNAVars(self.ordered_vars)
114            self._defineNAAuxVars(aux_vars)
115            self._defineNAGlobals()
116            self._defineNAComments()
117            self._defineGeneralHeader()
118            self.found_na = True
119
120
121    def _analyseVariables(self):
122        """
123        Method to examine the content of CDMS variables to return
124        a tuple of two lists containing variables and auxiliary variables
125        for the NASA Ames file object.
126        Variables not compatible with the first file are put in self.unused_vars
127        """
128        self.unused_vars = []
129        ffis_limited = False
130
131        highest_rank = -1
132        best_var = None
133        count = 0
134
135        # Need to get highest ranked variable (most dimensions) so that we can work out FFI
136        for var in self.vars:
137            msg = "Analysing: %s" % var.id
138            self.output_message.append(msg)
139            count = count + 1
140            # get rank
141            rank = var.rank()
142
143            # Deal with singleton variables
144            if rank == 0: 
145                self.rank_zero_vars.append(var)
146                self.rank_zero_var_ids.append(var.id)
147                continue
148
149            # Update highest if highest found or if equals highest with bigger size
150            try:
151                var.size = var.size() ; best_var.size = best_var.size()
152            except:
153                pass
154            if rank > highest_rank or (rank == highest_rank and var.size > best_var.size):
155                highest_rank = rank
156                best_var = var
157                best_var_index = count - 1
158
159        # If all are zero ranked variables or no vars identified/found then we cannot write any to NASA Ames and return ([], [])
160        if len(self.rank_zero_vars) == len(self.vars) or best_var is None: 
161            return ([], [])
162
163        # Now start to sort the variables into main and auxiliary
164        vars_for_na = [best_var]
165        aux_vars_for_na = []
166        shape = best_var.shape
167        number_of_dims = len(shape)
168        self.na_dict["NIV"] = number_of_dims
169
170
171        # If 2D then do a quick test to see if 2310 is feasible (i.e. uniformly spaced 2nd axis)
172        if number_of_dims == 2:
173            ffis_limited = [2010, 2110]
174            axis = best_var.getAxis(1)
175            if cdms_utils.axis_utils.isUniformlySpaced(axis):
176                ffis_limited.append(2310)
177
178        # Get the axes for the main variable being used
179        best_var_axes = best_var.getAxisList()
180       
181        # Get other variables into a list and analyse them
182        rest_of_the_vars = self.vars[:best_var_index] + self.vars[(best_var_index + 1):]
183
184        for var in rest_of_the_vars:
185
186            if var.id in self.rank_zero_var_ids: continue
187
188            # What to do with variables that have different number of dimensions or different shape
189            if len(var.shape) != number_of_dims or var.shape != shape: 
190                # Could it be an auxiliary variable?
191                if len(var.shape) != 1: 
192                    self.unused_vars.append(var)
193                    continue
194
195                first_axis = var.getAxis(0)
196                # Check if axis is identical to first axis of main best variable, if so, can be auxiliary var
197                if cdms_utils.axis_utils.areAxesIdentical(best_var_axes[0], first_axis) == False: 
198
199                    # If not identical, then it might still qualify as an auxiliary every n time points - valid for 1020
200                    if len(var.shape) == 1:
201                        nvpm = cdms_utils.axis_utils.isAxisRegularlySpacedSubsetOf(first_axis, best_var_axes[0])
202                        # NVPM is the number of implied values which is equal to (len(ax2)/len(ax1))
203                        if nvpm:
204                            ffis_limited = [1020]
205                            self.na_dict["NVPM"] = nvpm
206                        else: # if returned False, i.e. not regular subset axis
207                            self.unused_vars.append(var)
208                    else:
209                        self.unused_vars.append(var)
210                        continue
211
212                else:
213                    # This could be used as a standard auxiliary variable
214                    if ffis_limited in ([1020],):
215                        # Already fixed on 1020 and cannot collect incompatible FFI vars so do not use
216                        self.unused_vars.append(var)
217                    else:
218                        aux_vars_for_na.append(var) 
219
220            else:
221                this_var_axes = var.getAxisList()
222
223                # Loop through dimensions
224                for i in range(number_of_dims):           
225                    if cdms_utils.axis_utils.areAxesIdentical(best_var_axes[i], this_var_axes[i]) == False:
226                        self.unused_vars.append(var)
227                        break
228                else:
229                    # OK, I think the current variable is compatible to write with the best variable along with a NASA Ames file
230                    vars_for_na.append(var)
231
232        # Send vars_for_na AND aux_vars_for_na to a method to check if they have previously been mapped
233        # from NASA Ames. In which case we'll write them back in the order they were initially read from the input file.
234        (vars_for_na, aux_vars_for_na) = self._reorderVarsIfPreviouslyNA(vars_for_na, aux_vars_for_na)
235
236        # Get the FFI
237        self.na_dict["FFI"] = self._decideFileFormatIndex(number_of_dims, aux_vars_for_na, ffis_limited)
238        return (vars_for_na, aux_vars_for_na)
239
240
241    def _reorderVarsIfPreviouslyNA(self, vars_for_na, aux_vars_for_na):
242        """
243        Re-order if they previously came from NASA Ames files (i.e. including the
244        attribute 'nasa_ames_var_number'). Return re-ordered or unchanged pair of
245        (vars_for_na, aux_vars_for_na).
246        """
247        # THIS SHOULD REALLY BE DONE IN A LOOP
248        # First do the main variables
249        ordered_vars = [None] * 1000 # Make a long list to put vars in
250        # Create a list of other variables to collect up any that are not labelled as nasa ames variables
251        other_vars = []
252        for var in vars_for_na:
253            if hasattr(var, "nasa_ames_var_number"):
254                ordered_vars[var.nasa_ames_var_number[0]] = var
255            else:
256                other_vars.append(var)
257
258        # Remake vars_for_na now in new order and clean out any that are "None"
259        vars_for_na = []
260        for var in ordered_vars:
261            if var != None: 
262                vars_for_na.append(var)
263
264        vars_for_na = vars_for_na + other_vars
265
266        # Now re-order the Auxiliary variables if they previously came from NASA
267        ordered_aux_vars = [None] * 1000
268        other_aux_vars = []
269
270        for var in aux_vars_for_na:
271            if hasattr(var, "nasa_ames_aux_var_number"):
272                ordered_aux_vars[var.nasa_ames_aux_var_number[0]] = var
273            else:
274                other_aux_vars.append(var)
275
276        # Remake aux_vars_for_na now in order
277        aux_vars_for_na = []
278        for var in ordered_aux_vars:
279            if var != None: 
280                aux_vars_for_na.append(var)
281
282        aux_vars_for_na = aux_vars_for_na + other_aux_vars
283        return (vars_for_na, aux_vars_for_na)
284
285
286    def _decideFileFormatIndex(self, number_of_dims, aux_vars_for_na, ffis_limited=False):
287        """
288        Based on the number of dimensions and the NASA Ames dictionary return
289        the File Format Index.
290        If there is a choice then make the most sensible selection.
291        If the user has specified a 'requested_ffi' then try and deliver
292        that. Raise an error if not possible.
293        """
294        # If ffis_limited is set then must use one of those
295        if self.requested_ffi and ffis_limited:
296            if self.requested_ffi not in ffis_limited:
297                raise Exception("Cannot write this data to FFI '" + str(self.requested_ffi) + "', can only write to: " + str(ffis_limited) + ".")
298            else:
299                return self.requested_ffi
300
301        # Base the sub-selection on number of dimensions
302        if number_of_dims > 4:
303            raise Exception("Cannot write variables defined against greater than 4 axes in NASA Ames format.")
304        elif number_of_dims > 2: 
305            ffi = 10 + (number_of_dims * 1000)
306        elif number_of_dims == 2:
307            if self.requested_ffi in (2010, 2110, 2310):
308                ffi = self.requested_ffi
309            else:
310                ffi = 2010
311        else:
312            if len(aux_vars_for_na) > 0 or (self.na_dict.has_key("NAUXV") and self.na_dict["NAUXV"] > 0):
313                ffi = 1010
314            else:
315                ffi = 1001
316
317        if self.requested_ffi and ffi != self.requested_ffi:
318            raise Exception("Cannot write this data to FFI '" + str(self.requested_ffi) + "', can only write to: " + str(ffi) + ".")
319        return ffi
320
321
322    def _defineNAVars(self, vars):
323        """
324        Method to define NASA Ames file object variables and their
325        associated metadata.
326        """
327        self.na_dict["NV"] = len(vars)
328        self.na_dict["VNAME"] = []
329        self.na_dict["VMISS"] = []
330        self.na_dict["VSCAL"] = []
331        self.na_dict["V"] = []
332
333        for var in vars:
334            name = cdms_utils.var_utils.getBestName(var)
335            self.na_dict["VNAME"].append(name)
336            miss = cdms_utils.var_utils.getMissingValue(var)
337            if type(miss) not in (type(1.2), type(1), type(1L)): 
338                miss = miss[0]
339            self.na_dict["VMISS"].append(miss)
340            self.na_dict["VSCAL"].append(1)
341            # Populate the variable list with the array
342            self.na_dict["V"].append(var.tolist())
343
344            # Create independent variable info
345            if not self.na_dict.has_key("X"):
346                # Set up lists ready to populate with values
347                self.na_dict["NXDEF"] = []
348                self.na_dict["NX"] = []
349
350                self.ax0 = var.getAxis(0)
351
352                self.na_dict["X"] = [self.ax0[:].tolist()]
353                self.na_dict["XNAME"] = [cdms_utils.var_utils.getBestName(self.ax0)]
354
355                if len(self.ax0) == 1:
356                    self.na_dict["DX"] = [0]
357                else:
358                    incr = self.ax0[1] - self.ax0[0]
359                    # Set default increment as gap between first two
360                    self.na_dict["DX"] = [incr]
361                    # Now overwrite it as zero if non-uniform interval in axis
362                    for i in range(1, len(self.ax0)):
363                        if (self.ax0[i] - self.ax0[i - 1]) != incr:
364                            self.na_dict["DX"] = [0]
365                            break
366
367                # If 1D only then "X" should only be a list and not list of lists
368                if self.na_dict["FFI"] in (1001, 1010, 1020):
369                    self.na_dict["X"] = self.na_dict["X"][0]
370
371                # If FFI is 1020 need to reduce axis down to reduced values as most are implied
372                if self.na_dict["FFI"] == 1020: 
373                    vals = self.na_dict["X"]
374                    self.na_dict["X"] = vals[0:len(vals):self.na_dict["NVPM"]] 
375
376                # Now add the rest of the axes to the self.na_dict objects
377                for axis in var.getAxisList()[1:]:
378                    self._appendAxisDefinition(axis)
379
380                # If FFI is 2110 then need to modify the "NX" and "X" lists to cope with odd shape
381                # Also need to add NX to auxiliary variables
382                if self.na_dict["FFI"] == 2110:
383                    new_x = []
384                    new_nx = []
385                    ax2_values = var.getAxis(1)[:].tolist()
386
387                    for i in self.ax0[:]:
388                        new_x.append([i, ax2_values])
389                        new_nx.append(len(ax2_values))
390
391                    # Re-assign to new lists
392                    self.na_dict["NX"] = new_nx
393                    self.na_dict["X"] = new_x                   
394
395                    # Now auxiliary variable info here with independent var info
396                    # First aux var is NX
397                    self.na_dict["A"] = [self.na_dict["NX"][:]]
398                    ind_var_name = self.na_dict["XNAME"][0]
399                    self.na_dict["ANAME"] = ["Number of '%s' values recorded in subsequent data records" % ind_var_name]
400                    self.na_dict["AMISS"] = [-9999.999]
401                    self.na_dict["ASCAL"] = [1.0]
402
403                # If FFI is 2310 then need to modify na_dict items for that
404                elif self.na_dict["FFI"] == 2310:
405                    new_x = []
406                    new_nx = []
407                    new_dx = []
408                    ax2_values = var.getAxis(1)[:].tolist()
409                    incr = ax2_values[1] - ax2_values[0]
410
411                    for i in self.ax0[:]:
412                        new_x.append([i, ax2_values])
413                        new_nx.append(len(ax2_values))
414                        new_dx.append(incr)
415
416                    # Re-assign to new lists
417                    self.na_dict["NX"] = new_nx
418                    self.na_dict["X"] = new_x
419                    self.na_dict["DX"] = new_dx
420
421                    # Now auxiliary variable info here with independent var info
422                    # First three aux vars are NX, X0 and DX
423                    self.na_dict["A"] = []
424                    self.na_dict["A"].append(self.na_dict["NX"][:])
425                    self.na_dict["A"].append([i[1][0] for i in self.na_dict["X"]])
426                    self.na_dict["A"].append(self.na_dict["DX"][:])
427                    ind_var_name = self.na_dict["XNAME"][0]
428                    self.na_dict["ANAME"] = ["Number of '%s' values recorded in subsequent data records" % ind_var_name,
429                                             "'%s' value for first data point" % ind_var_name,
430                                             "'%s' increment" % ind_var_name]
431                    self.na_dict["AMISS"] = [-9999.999, -9999.999, -9999.999]
432                    self.na_dict["ASCAL"] = [1.0, 1.0, 1.0]
433 
434
435    def _defineNAAuxVars(self, aux_vars):
436        """
437        Method to define NASA Ames file object auxiliary variables and their
438        associated metadata. Note that "A" may already have content if
439        independent variable items (relating to "X") are defined as aux vars.
440        """
441        # Initialise aux var itesms as empty lists unless already defined when
442        # setting up independent variables
443        for item in ("ANAME", "AMISS", "ASCAL", "A"):
444            if not self.na_dict.has_key(item):
445                self.na_dict[item] = [] 
446
447        for var in aux_vars:
448            name = cdms_utils.var_utils.getBestName(var)
449            self.na_dict["ANAME"].append(name)
450            miss = cdms_utils.var_utils.getMissingValue(var)
451            if type(miss) != type(1.1):  miss = miss[0]
452            self.na_dict["AMISS"].append(miss)
453            self.na_dict["ASCAL"].append(1)
454            # Populate the variable list with the array
455            self.na_dict["A"].append(var[:].tolist())
456
457        self.na_dict["NAUXV"] = len(self.na_dict["A"])
458
459    def _appendAxisDefinition(self, axis):
460        """
461        Method to create the appropriate NASA Ames file object
462        items associated with an axis (independent variable in
463        NASA Ames). It appends to the various self.na_dict containers.
464        """
465        length = len(axis)
466
467        self.na_dict["NX"].append(length)
468        self.na_dict["XNAME"].append(cdms_utils.var_utils.getBestName(axis))
469        # If only one item in axis values
470        if length < 2:
471            self.na_dict["DX"].append(0)
472            self.na_dict["NXDEF"].append(length)
473            self.na_dict["X"].append(axis[:].tolist())       
474            return
475   
476        incr = axis[1] - axis[0]
477        for i in range(1, length):
478            if (axis[i] - axis[i - 1]) != incr:
479                self.na_dict["DX"].append(0)
480                self.na_dict["NXDEF"].append(length)
481                self.na_dict["X"].append(axis[:].tolist())
482                break
483        else: # If did not break out of the loop
484            max_length = length
485            if length > 3: 
486                max_length = 3
487            self.na_dict["DX"].append(incr)
488            self.na_dict["NXDEF"].append(max_length)
489            self.na_dict["X"].append(axis[:max_length])
490
491    def _defineNAGlobals(self):
492        """
493        Maps CDMS (NetCDF) global attributes into NASA Ames Header fields.
494        """
495        # Check if we should add to it with locally set rules
496        local_attributes = nappy.utils.getLocalAttributesConfigDict()
497        local_nc_atts = local_attributes["nc_attributes"]
498         
499        for att, value in local_nc_atts.items():
500            if not nc_to_na_map.has_key(att):
501                nc_to_na_map[key] = value
502
503        self.extra_comments = [[],[],[]]  # Normal comments, special comments, other comments
504        convention_or_reference_comments = []
505
506        for key in self.globals.keys():
507            if key != "first_valid_date_of_data" and type(self.globals[key]) \
508                                       not in (type("s"), type(1.1), type(1)):
509                continue
510
511            # Loop through keys of header/comment items to map
512            if key in nc_to_na_map.keys():
513                if key == "history":
514                    time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
515                    history = "History:  %s - Converted to NASA Ames format using nappy-%s.\n  %s" % \
516                                                 (time_string, version, self.globals[key])
517                    history = history.split("\n") 
518                    self.history = []
519                    for h in history:
520                        if h[:8] != "History:" and h[:1] != "  ": 
521                            h = "  " + h
522                        self.history.append(h) 
523                   
524                elif key == "institution":
525                    # If fields came from NA then extract appropriate fields.
526                    match = re.match(r"(.*)\s+\(ONAME from NASA Ames file\);\s+(.*)\s+\(ORG from NASA Ames file\)\.", 
527                             self.globals[key])
528                    if match:
529                        self.na_dict["ONAME"] = match.groups()[0]
530                        self.na_dict["ORG"] = match.groups()[1]
531                    else:
532                        self.na_dict["ONAME"] = self.globals[key]
533                        self.na_dict["ORG"] = self.globals[key]
534                    # NOTE: should probably do the following search and replace on all string lines
535                    self.na_dict["ONAME"] = self.na_dict["ONAME"].replace("\n", "  ")
536                    self.na_dict["ORG"] = self.na_dict["ORG"].replace("\n", "  ")
537
538                elif key == "comment":
539                    # Need to work out if they are actually comments from NASA Ames in the first place
540                    comment_lines = self.globals[key].split("\n")
541                    normal_comments = []
542                    normal_comm_flag = None
543                    special_comments = []
544                    special_comm_flag = None
545
546                    for line in comment_lines:
547                        if line.find(hp["sc_start"]) > -1:
548                            special_comm_flag = 1
549                        elif line.find(hp["sc_end"]) > -1:
550                            special_comm_flag = None
551                        elif line.find(hp["nc_start"]) > -1:
552                            normal_comm_flag = 1
553                        elif line.find(hp["nc_end"]) > -1:
554                            normal_comm_flag = None
555                        elif special_comm_flag == 1:
556                            special_comments.append(line)
557                        elif normal_comm_flag == 1:
558                            normal_comments.append(line)
559                        elif line.find(hp["data_next"]) > -1:
560                            pass
561                        else:
562                            normal_comments.append(line)   
563
564                    self.extra_comments = [special_comments, normal_comments, []]   
565
566                elif key == "first_valid_date_of_data":
567                    self.na_dict["DATE"] = self.globals[key]
568
569                elif key in ("Conventions", "references"):
570                    #convention_or_reference_comments.append("%s:   %s" % (key, self.globals[key]))
571                    self.extra_comments[2].append("%s:   %s" % (key, self.globals[key]))
572                else:
573                    self.na_dict[nc_to_na_map[key]] = self.globals[key]
574            else:
575                self.extra_comments[2].append("%s:   %s" % (key, self.globals[key]))
576        return
577
578
579    def _defineNAComments(self, normal_comments=[], special_comments=[]):
580        """
581        Defines the Special and Normal comments sections in the NASA Ames file
582        object - including information gathered from the defineNAGlobals method.
583
584        Starts with values provided for normal_comments and special_comments.
585        """
586        if hasattr(self, "ncom"):  normal_comments = self.ncom + normal_comments
587
588        NCOM = []
589        for ncom in normal_comments:
590            NCOM.append(ncom)
591
592        if len(NCOM) > 0:   NCOM.append("")
593
594        # Use third item in self.extra_comments and adds to NCOM
595        if len(self.extra_comments[2]) > 0:
596            for excom in self.extra_comments[2]:
597                NCOM.append(excom)
598
599        if len(self.extra_comments[1]) > 0: 
600            NCOM.append(hp["addl_globals"])
601            for excom in self.extra_comments[1]:
602                NCOM.append(excom)
603
604        if hasattr(self, "history"):
605            for h in self.history:
606                NCOM.append(h)
607       
608        # When NCOM has been defined then surround it in some extras
609        if len(NCOM) > 0:
610            NCOM.insert(0, hp["nc_start"]) 
611            NCOM.append("")
612            NCOM.append(hp["nc_end"])
613            NCOM.append(hp["data_next"])
614
615        spec_comm_flag = None
616        # Start with special_comments added in
617        SCOM = []
618
619        # Uses first item in self.extra_comments to start SCOM
620        special_comments = special_comments + self.extra_comments[0]
621
622        if len(special_comments) > 0: 
623            SCOM = [hp["sc_start"]]
624            spec_comm_flag = 1
625
626        for scom in special_comments:
627            SCOM.append(scom)
628
629        used_var_atts = ("id",  "missing_value", "fill_value", 
630                   "nasa_ames_var_number", "nasa_ames_aux_var_number")
631        var_comm_flag = None
632
633        # Create a string for the Special comments to hold rank-zero vars
634        rank_zero_vars_string = []
635
636        for var in self.rank_zero_vars:
637            rank_zero_vars_string.append("  Variable %s: %s" % (var.id, cdms_utils.var_utils.getBestName(var)))
638
639            for att in var.attributes.keys():
640                value = var.attributes[att]
641
642                if type(value) in (type("s"), type(1.0), type(1)):
643
644                    rank_zero_vars_string.append("    %s = %s" % (att, var.attributes[att]))
645
646        if len(rank_zero_vars_string) > 0:
647            rank_zero_vars_string.insert(0, hp["sing_start"])
648            rank_zero_vars_string.append(hp["sing_end"])
649
650        for var in self.ordered_vars:
651            varflag = "unused"
652            var_name_written = False
653
654            name = cdms_utils.var_utils.getBestName(var)
655
656            for scom,value in var.attributes.items():
657                if type(value) in (type([]), type(N.array([0]))) and len(value) == 1:
658                    value = value[0]
659
660                if type(value) in (type("s"), type(1.1), type(1)) and scom not in used_var_atts:
661                    if varflag == "unused":
662                        if var_comm_flag == None:
663                            var_comm_flag = 1
664
665                    if spec_comm_flag == None:
666                        SCOM = [hp["sc_start"]] + rank_zero_vars_string
667                        SCOM.append(hp["addl_vatts"])
668                        SCOM.append(hp["ncatts_start"])
669                        varflag = "using" 
670                        spec_comm_flag = 1
671
672                    if var_name_written == False:
673                        SCOM.append("  Variable %s: %s" % (var.id, name))
674                        var_name_written = True
675
676                    SCOM.append("    %s = %s" % (scom, value))
677
678        if var_comm_flag == 1: 
679            SCOM.append(hp["ncatts_end"])
680        if spec_comm_flag == 1:
681            SCOM.append(hp["sc_end"])
682
683        # Strip out empty lines (or returns)
684        NCOM_cleaned = []
685        SCOM_cleaned = []
686
687        for c in NCOM:
688            if c.strip() not in ("", " ", "  "):
689                # Replace new lines within one attribute with a newline and tab so easier to read
690                lines = c.split("\n")
691                for line in lines:
692                    if line != lines[0]: 
693                        line = "  " + line
694
695                    NCOM_cleaned.append(line)
696
697        for c in SCOM:
698            if c.strip() not in ("", " ", "  "):
699                # Replace new lines within one attribute with a newline and tab so easier to read
700                lines = c.split("\n")
701                for line in lines:
702                    if line != lines[0]: 
703                        line = "  " + line
704
705                    SCOM_cleaned.append(line)
706
707        self.na_dict["NCOM"] = NCOM_cleaned
708        self.na_dict["NNCOML"] = len(self.na_dict["NCOM"])
709        self.na_dict["SCOM"] = SCOM_cleaned
710        self.na_dict["NSCOML"] = len(self.na_dict["SCOM"])
711        return
712
713
714    def _defineGeneralHeader(self, header_items={}):
715        """
716        Defines known header items and overwrites any with header_items
717        key/value pairs.
718        """
719        warning_message = "Nappy Warning: Could not get the first date in the file. You will need to manually edit the output file."
720
721        # Check if DATE field previously known in NASA Ames file
722        time_now = [int(i) for i in time.strftime("%Y %m %d", time.localtime(time.time())).split()]
723
724        if not self.na_dict.has_key("RDATE"):
725            self.na_dict["RDATE"] = time_now
726
727        if self.ax0.isTime():
728            # Get first date in list
729            try:
730                (unit, start_date) = re.match("(\w+)\s+?since\s+?(\d+-\d+-\d+)", self.ax0.units).groups()           
731                comptime = cdtime.s2c(start_date)
732                first_day = comptime.add(self.na_dict["X"][0], getattr(cdtime, unit.capitalize()))
733                self.na_dict["DATE"] = [int(i) for i in str(first_day).split(" ")[0].replace("-", " ").split()]
734            except:
735                msg = warning_message
736                log.info(msg)
737                self.output_message.append(msg)
738                self.na_dict["DATE"] = [999] * 3 
739        else: 
740            if not self.na_dict.has_key("DATE"):
741                msg = warning_message
742                log.info(msg)
743                self.output_message.append(msg)
744                self.na_dict["DATE"] = [999] * 3 
745            else:
746                pass # i.e. use existing DATE
747
748        self.na_dict["IVOL"] = 1
749        self.na_dict["NVOL"] = 1
750        for key in header_items.keys():
751             self.na_dict[key] = header_items[key]
752
Note: See TracBrowser for help on using the repository browser.