source: nappy/trunk/nappy/na_file/na_file.py @ 5368

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

Replace print with logging

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"""
6naFile.py
7=========
8
9A containter module for the mixin base class NAFile that is subclassed
10for individual FFIs. Each FFI class is held in an individual file.
11
12"""
13   
14# 08/05/04 updated by selatham for bug fixes and new write methods
15
16# Imports from python standard library
17import sys
18import time
19import re
20import StringIO
21
22# Imports from nappy package
23import nappy.na_file.na_core
24import nappy.utils.text_parser
25import nappy.utils.common_utils
26import nappy.na_error
27
28default_delimiter = nappy.utils.getDefault("default_delimiter")
29default_float_format = nappy.utils.getDefault("default_float_format")
30getAnnotation = nappy.utils.common_utils.getAnnotation
31wrapLine = nappy.utils.common_utils.annotateLine
32wrapLines = nappy.utils.common_utils.annotateLines
33stripQuotes = nappy.utils.common_utils.stripQuotes
34
35
36class NAFile(nappy.na_file.na_core.NACore):
37
38    """
39    NAFile class is a sub-class of NACore abstract classes.
40    NAFile is also an abstract class and should not be called directly.
41   
42    NAFile holds all the methods that are common to either all or more than
43    one NASA Ames FFI class. These methods set up the main read and write
44    functionality for the NASA Ames format.
45
46    When a sub-class of NAFile is called with a read ('r' - default)
47    mode the header in the file is automatically read. To read the data
48    section the user must call the 'readData' method.
49     
50    When a sub-class of NAFile is called with the write ('w') mode
51    then the file is opened but nothing is written. The user must then
52    send an 'na_dict' object to the 'write' method to write the output.
53    The output file is then flushed to ensure the data is written even if
54    the user forgets to close it.
55    """
56
57    def __init__(self, filename, mode="r", na_dict={}): 
58        """
59        Initialization of class, decides if user wishes to read or write
60        NASA Ames file.
61        """
62        nappy.na_file.na_core.NACore.__init__(self)
63        self.filename = filename
64        self._open(mode)
65        self.mode = mode
66        self.na_dict = na_dict
67
68        if self.mode == "r":
69            self._normalized_X = True
70            self.readHeader()
71        elif self.mode == "w":
72            # Self flag to check if data written
73            self.data_written = False
74        else:
75            raise "Unknown file mode '%s'." % self.mode
76
77       
78    def _open(self, mode):
79        "Wrapper to builtin open file function."
80        self.file = open(self.filename, mode)
81        self.is_open = True
82
83    def write(self, delimiter=default_delimiter, float_format=default_float_format,
84              annotation=False, no_header=False):
85        """
86        Writes an na_dict to the file and then flushes it to ensure data not
87        being buffered.
88        If annotation is True then add annotation column to left of file.
89        If no_header is True then suppress writing the header and only write the data section.
90        """ 
91        self.delimiter = delimiter
92        self.float_format = float_format
93        self.format = float_format + delimiter
94        self.annotation = annotation
95       
96        # Raise errors if dangerous behaviour
97        if self.mode != "w":
98            raise Exception("WARNING: Cannot write to read-only file. Can only write to NA file object when mode='w'.")
99
100        if self.data_written == True:
101            raise Exception("WARNING: Cannot write multiple NASA Ames dictionaries to a single file. Please open a new NASA Ames file instance to write new data to.")
102
103        if self.is_open == False:
104            raise Exception("WARNING: NASA Ames file instance is closed and cannot be written to.")
105   
106        # Parse na_dict then write header and data
107        self._parseDictionary()
108        self.header = StringIO.StringIO()
109
110        if no_header == False:
111            self.writeHeader()
112
113        self.writeData()
114        self.file.flush()
115       
116        # Set flag to make sure cannot try and write more data
117        self.data_written = True
118         
119    def close(self):
120        "Wrapper to builtin close file function."
121        self.file.close()
122        self.is_open = False
123
124    def _parseDictionary(self):
125        """
126        Parser for the optional na_dict argument containing a dictionary
127        of NASA Ames internal variables. These are saved as instance attributes
128        with the name used in the NASA Ames documentation.
129        """
130        for i in self.na_dict.keys():
131            setattr(self, i, self.na_dict[i])
132
133    def _readTopLine(self):
134        """
135        Reads number of header lines and File Format Index from top line.
136        Also assigns a value to NIV for the number of independent variables
137        based on the first character in the FFI.
138
139        Returns NLHEAD and FFI in a tuple.
140        """
141        (self.NLHEAD, self.FFI) = nappy.utils.text_parser.readItemsFromLine(self.file.readline(), 2, int)
142        self.NIV = int(self.FFI/1000)
143        return (self.NLHEAD, self.FFI)
144
145    def _readLines(self, nlines):
146        "Reads nlines lines from a file and returns them in a list."
147        lines = []
148        for i in range(nlines):
149            lines.append(self.file.readline().strip())
150        return lines
151
152    def _checkForBlankLines(self, datalines):
153        """
154        Searches for empty lines in the middle of the data section and raises
155        as error if found. It ignores empty lines at the end of the file but
156        strips them out before returning a list of lines for reading.
157        """
158        empties = None
159        count = 0
160        rtlines = []
161        for line in datalines:
162            if line.strip() == "":
163                empties = 1
164            else:
165                if empties == 1:   # If data line found after empty line then raise
166                    raise Exception("Empty line found in data section at line: " + `count`)
167                else:
168                    rtlines.append(line)
169            count = count + 1
170        return rtlines
171
172
173    def _readCommonHeader(self):
174        """
175        Reads the header section common to all NASA Ames files.
176        """
177        self._readTopLine()
178        self.ONAME = nappy.utils.text_parser.readItemFromLine(self.file.readline(), str)
179        self.ORG = nappy.utils.text_parser.readItemFromLine(self.file.readline(), str)
180        self.SNAME = nappy.utils.text_parser.readItemFromLine(self.file.readline(), str)
181        self.MNAME = nappy.utils.text_parser.readItemFromLine(self.file.readline(), str)
182        (self.IVOL, self.NVOL) = nappy.utils.text_parser.readItemsFromLine(self.file.readline(), 2, int)
183        dates = nappy.utils.text_parser.readItemsFromLine(self.file.readline(), 6, int)
184        (self.DATE, self.RDATE) = (dates[:3], dates[3:])
185
186    def _writeCommonHeader(self):
187        """
188        Writes the header section common to all NASA Ames files.
189        """
190        #Line 1 if often overwritten at _fixHeaderLength
191        self.header.write(wrapLine("NLHEAD_FFI", self.annotation, self.delimiter, "%d%s%d\n" % (self.NLHEAD, self.delimiter, self.FFI)))
192        self.header.write(getAnnotation("ONAME", self.annotation, delimiter = self.delimiter) + stripQuotes(self.ONAME) + "\n")
193        self.header.write(getAnnotation("ORG", self.annotation, delimiter = self.delimiter) + stripQuotes(self.ORG) + "\n")
194        self.header.write(getAnnotation("SNAME", self.annotation, delimiter = self.delimiter) + stripQuotes(self.SNAME) + "\n")
195        self.header.write(getAnnotation("MNAME", self.annotation, delimiter = self.delimiter) + stripQuotes(self.MNAME) + "\n")
196        self.header.write(wrapLine("IVOL_NVOL", self.annotation, self.delimiter, "%d%s%d\n" % (self.IVOL, self.delimiter, self.NVOL)))
197        line = "%d %d %d%s%d %d %d\n" % (self.DATE[0], self.DATE[1], self.DATE[2], self.delimiter, self.RDATE[0], self.RDATE[1], self.RDATE[2])
198        self.header.write(wrapLine("DATE_RDATE", self.annotation, self.delimiter, line))
199
200    def _readVariablesHeaderSection(self):
201        """
202        Reads the variables section of the header.
203        Assumes we are at the right point in the file.
204        """
205        self.NV = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
206        self.VSCAL = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NV, float)
207        self.VMISS = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NV, float)
208        self.VNAME = nappy.utils.text_parser.readItemsFromLines(self._readLines(self.NV), self.NV, str)
209
210    def _writeVariablesHeaderSection(self):
211        """
212        Writes the variables section of the header.
213        Assumes we are at the right point in the file.
214        """
215        self.header.write(wrapLine("NV", self.annotation, self.delimiter, "%d\n" % self.NV))
216        self.header.write(wrapLine("VSCAL", self.annotation, self.delimiter, (("%s" + self.delimiter) * (self.NV - 1) + "%s\n") % tuple(self.VSCAL)))
217        self.header.write(wrapLine("VMISS", self.annotation, self.delimiter, (("%s" + self.delimiter) * (self.NV - 1) + "%s\n") % tuple(self.VMISS)))
218        self.header.write(wrapLines("VNAME", self.annotation, self.delimiter, "%s\n" * self.NV % tuple(self.VNAME)))
219
220    def _readAuxVariablesHeaderSection(self):
221        """
222        Reads the auxiliary variables section of the header.
223        Assumes we are at the right point in the file.
224        """
225        self.NAUXV = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
226        if self.NAUXV > 0:       
227            self.ASCAL = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NAUXV, float)
228            self.AMISS = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NAUXV, float)
229            self.ANAME = nappy.utils.text_parser.readItemsFromLines(self._readLines(self.NAUXV), self.NAUXV, str)
230
231    def _writeAuxVariablesHeaderSection(self):
232        """
233        Writes the auxiliary variables section of the header.
234        Assumes we are at the right point in the file.
235        """
236        self.header.write(wrapLine("NAUXV", self.annotation, self.delimiter, "%d\n" % self.NAUXV))
237        if self.NAUXV > 0:
238            line = (("%s" + self.delimiter) * (self.NAUXV - 1) + "%s\n")  % tuple(self.ASCAL)
239            self.header.write(wrapLine("ASCAL", self.annotation, self.delimiter, line))
240            line = (("%s" + self.delimiter) * (self.NAUXV - 1) + "%s\n")  % tuple(self.AMISS)
241            self.header.write(wrapLine("AMISS", self.annotation, self.delimiter, line))
242            line = "%s\n" * self.NAUXV % tuple(self.ANAME)
243            self.header.write(wrapLines("ANAME", self.annotation, self.delimiter, line))
244
245    def _readCharAuxVariablesHeaderSection(self):
246        """
247        Reads the character-encoded auxiliary variables section of the header.
248        Assumes we are at the right point in the file.
249        """
250        self.NAUXV = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
251        self.NAUXC = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
252        nonCharAuxVars = self.NAUXV - self.NAUXC
253        if self.NAUXV > 0:
254            self.ASCAL = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, nonCharAuxVars, float)
255            self.AMISS = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, nonCharAuxVars, float)
256            self.LENA = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NAUXC, int)
257            for i in range(nonCharAuxVars):
258                self.LENA.insert(0, None)
259            self.AMISS = self.AMISS + nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NAUXC, str)   
260            self.ANAME = nappy.utils.text_parser.readItemsFromLines(self._readLines(self.NAUXV), self.NAUXV, str)       
261           
262    def _readComments(self):
263        """
264        Reads the special and normal comments sections.
265        Assumes we are at the right point in the file.
266        """       
267        self.NSCOML = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
268        self._readSpecialComments()
269        self.NNCOML = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
270        self._readNormalComments()
271
272    def _writeComments(self):
273        """
274        Writes the special and normal comments sections.
275        Assumes we are at the right point in the file.
276        """
277        self.header.write(wrapLine("NSCOML", self.annotation, self.delimiter, "%d\n" % self.NSCOML))
278        self.header.write(wrapLines("SCOM", self.annotation, self.delimiter, "%s\n" * self.NSCOML % tuple(self.SCOM)))
279        self.header.write(wrapLine("NNCOML", self.annotation, self.delimiter, "%d\n" % self.NNCOML))
280        self.header.write(wrapLines("NCOM", self.annotation, self.delimiter, "%s\n" * self.NNCOML % tuple(self.NCOM)))
281
282    def _fixHeaderLength(self):
283        """
284        Takes the self.header StringIO object and counts the number of lines
285        and corrects the NLHEAD value in the header line.
286        Resets to start of self.header.
287        """
288        self.header.seek(0)
289        lines = self.header.readlines()
290        headlength = len(lines)
291        lines[0] = wrapLine("NLHEAD_FFI", self.annotation, self.delimiter, "%d%s%d\n" % (headlength, self.delimiter, self.FFI))
292        self.header = StringIO.StringIO("".join(lines))
293        self.header.seek(0) 
294
295    def _readSpecialComments(self):
296        """
297        Reads the special comments section.       
298        Assumes that we are at the right point in the file and that NSCOML
299        variable is known.
300        """
301        self.SCOM = self._readLines(self.NSCOML)
302        return self.SCOM
303
304    def _readNormalComments(self):
305        """
306        Reads the normal comments section.       
307        Assumes that we are at the right point in the file and that NNCOML
308        variable is known.
309        """
310        self.NCOM = self._readLines(self.NNCOML)
311        return self.NCOM
312
313    def readData(self):
314        """
315        Reads the data section of the file. This method actually calls a number
316        of FFI specific methods to setup the data arrays (lists of lists) and
317        read the various data sections.
318
319        This method can be called directly by the user.
320        """
321        self._setupArrays()
322        datalines = open(self.filename).readlines()[self.NLHEAD:]
323        datalines = self._checkForBlankLines(datalines)
324
325        # Set up loop over unbounded indpendent variable
326        m = 0   # Unbounded independent variable mark       
327        while len(datalines) > 0:
328            datalines = self._readData1(datalines, m)
329            datalines = self._readData2(datalines, m)
330            m = m + 1
331           
Note: See TracBrowser for help on using the repository browser.