OB.DAAC Logo
NASA Logo
Ocean Color Science Software

ocssw V2022
SB_support.py
Go to the documentation of this file.
1 """ Module for manipulating data from NASA GSFC SeaBASS files.
2 
3 Author: Joel Scott, SAIC / NASA GSFC Ocean Ecology Lab
4 
5 Notes:
6 * This module is designed to work with files that have been properly
7  formatted according to SeaBASS guidelines (i.e. Files that passed FCHECK).
8  Some error checking is performed, but improperly formatted input files
9  could cause this script to error or behave unexpectedly. Files
10  downloaded from the SeaBASS database should already be properly formatted,
11  however, please email seabass@seabass.gsfc.nasa.gov and/or the contact listed
12  in the metadata header if you identify problems with specific files.
13 
14 * It is always HIGHLY recommended that you check for and read any metadata
15  header comments and/or documentation accompanying data files. Information
16  from those sources could impact your analysis.
17 
18 * Compatibility: This module was developed for Python 3.6, using Python 3.6.3
19 
20 /*=====================================================================*/
21  NASA Goddard Space Flight Center (GSFC)
22  Software distribution policy for Public Domain Software
23 
24  The readsb code is in the public domain, available without fee for
25  educational, research, non-commercial and commercial purposes. Users may
26  distribute this code to third parties provided that this statement appears
27  on all copies and that no charge is made for such copies.
28 
29  NASA GSFC MAKES NO REPRESENTATION ABOUT THE SUITABILITY OF THE SOFTWARE
30  FOR ANY PURPOSE. IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED
31  WARRANTY. NEITHER NASA GSFC NOR THE U.S. GOVERNMENT SHALL BE LIABLE FOR
32  ANY DAMAGE SUFFERED BY THE USER OF THIS SOFTWARE.
33 /*=====================================================================*/
34 
35 """
36 
37 #==========================================================================================================================================
38 
39 import re
40 from datetime import datetime
41 from collections import OrderedDict
42 
43 #==========================================================================================================================================
44 
45 def is_number(s):
46 
47  """
48  is_number determines if a given string is a number or not, does not handle complex numbers
49  returns True for int, float, or long numbers, else False
50  syntax: is_number(str)
51  """
52 
53  try:
54  float(s) # handles int, long, and float, but not complex
55  except ValueError:
56  return False
57  return True
58 
59 #==========================================================================================================================================
60 
61 def is_int(s):
62 
63  """
64  is_int determines if a given string is an integer or not, uses int()
65  returns True for int numbers, else False
66  syntax: is_int(str)
67  """
68 
69  try:
70  int(s) # handles int
71  except ValueError:
72  return False
73  return True
74 
75 #==========================================================================================================================================
76 
77 def doy2mndy(yr, doy):
78 
79  """
80  doy2mndy returns the month and day of month as integers
81  given year and julian day
82  syntax: [mn, dy] = doy2mndy(yr, doy)
83  """
84 
85  from datetime import datetime
86 
87  dt = datetime.strptime('{:04d}{:03d}'.format(yr,doy), '%Y%j')
88 
89  return int(dt.strftime('%m')),int(dt.strftime('%d'))
90 
91 #==========================================================================================================================================
92 
93 class readSB:
94  """ Read an FCHECK-verified SeaBASS formatted data file.
95 
96  Returned data structures:
97  .filename = name of data file
98  .headers = dictionary of header entry and value, keyed by header entry
99  .comments = list of strings containing the comment lines from the header information
100  .missing = fill value as a float used for missing data, read from header
101  .variables = dictionary of field name and unit, keyed by field name
102  .data = dictionary of data values, keyed by field name, returned as a list
103  .length = number of rows in the data matrix (i.e. the length of each list in data)
104  .bdl = fill value as a float used for below detection limit, read from header (empty if missing or N/A)
105  .adl = fill value as a float used for above detection limit, read from header (empty if missing or N/A)
106 
107  Returned sub-functions:
108  .fd_datetime() - Converts date and time information from the file's data matrix to a Python
109  list of datetime objects
110  .addDataToOutput(irow,var_name,units,var_value) - Adds or appends single data point to data matrix given row index, field name,
111  field units, and data value, handling fields & units headers and missing values
112  .writeSBfile(ofile) - Writes headers, comments, and data into a SeaBASS file specified by ofile
113  """
114 
115  def __init__(self, filename, mask_missing=True, mask_above_detection_limit=True, mask_below_detection_limit=True, no_warn=False):
116  """
117  Required arguments:
118  filename = name of SeaBASS input file (string)
119 
120  Optional arguments:
121  mask_missing = flag to set missing values to NaN, default set to True
122  mask_above_detection_limit = flag to set above_detection_limit values to NaN, default set to True
123  mask_below_detection_limit = flag to set below_detection_limit values to NaN, default set to True
124  no_warn = flag to suppress warnings, default set to False
125  """
126  self.filename = filename
127  self.headers = OrderedDict()
128  self.comments = []
129  self.variables = OrderedDict()
130  self.data = OrderedDict()
131  self.missing = ''
132  self.adl = ''
133  self.bdl = ''
134  self.pi = ''
135  self.length = 0
136  self.empty_col = []
137  self.data_use_warning = False
138  self.err_suffixes = ['_cv', '_sd', '_se', '_bincount']
139 
140  end_header = False
141 
142  try:
143  fileobj = open(self.filename,'r')
144 
145  except Exception as e:
146  raise Exception('Unable to open file for reading: {:}. Error: {:}'.format(self.filename,e))
147  return
148 
149  try:
150  lines = fileobj.readlines()
151  fileobj.close()
152 
153  except Exception as e:
154  raise Exception('Unable to read data from file: {:}. Error: {:}'.format(self.filename,e))
155  return
156 
157  """ Remove any/all newline and carriage return characters """
158  lines = [re.sub("[\r\n]+",'',line).strip() for line in lines]
159 
160  for line in lines:
161 
162  """ Extract header """
163  if not end_header \
164  and not '/begin_header' in line.lower() \
165  and not '/end_header' in line.lower() \
166  and not '!' in line:
167  try:
168  [h,v] = line.split('=', 1)
169  h = h.lower()
170 
171  h = h[1:]
172  self.headers[h] = v
173 
174  except:
175  raise Exception('Unable to parse header key/value pair. Is this a SeaBASS file: {:}\nLine: {:}'.format(self.filename,line))
176  return
177 
178  """ Extract fields """
179  if '/fields=' in line.lower() and not '!' in line:
180  try:
181  _vars = line.split('=', 1)[1].lower().split(',')
182  for var in _vars:
183  self.data[var] = []
184 
185  except Exception as e:
186  raise Exception('Unable to parse /fields in file: {:}. Error: {:}. In line: {:}'.format(self.filename,e,line))
187  return
188 
189  """ Extract units """
190  if '/units=' in line.lower() and not '!' in line:
191  _units = line.split('=', 1)[1].lower().split(',')
192 
193  """ Extract missing val """
194  if '/missing=' in line.lower() and not '!' in line:
195  try:
196  self.missing = float(line.split('=', 1)[1])
197 
198  except Exception as e:
199  raise Exception('Unable to parse /missing value in file: {:}. Error: {:}. In line: {:}'.format(self.filename,e,line))
200  return
201 
202  """ Extract optical depth warning """
203  if '/data_use_warning=' in line.lower() and not '!' in line:
204  self.data_use_warning = True
205 
206  """ Extract below detection limit """
207  if '/below_detection_limit=' in line.lower() and not '!' in line:
208  try:
209  self.bdl = float(line.split('=', 1)[1])
210 
211  except Exception as e:
212  raise Exception('Unable to parse /below_detection_limit value in file: {:}. Error: {:}. In line: {:}'.format(self.filename,e,line))
213  return
214 
215  """ Extract below detection limit """
216  if '/above_detection_limit=' in line.lower() and not '!' in line:
217  try:
218  self.adl = float(line.split('=', 1)[1])
219 
220  except Exception as e:
221  raise Exception('Unable to parse /above_detection_limit value in file: {:}. Error: {:}. In line: {:}'.format(self.filename,e,line))
222  return
223 
224  """ Extract PI """
225  if '/investigators=' in line.lower() and not '!' in line:
226  self.pi = line.split('=', 1)[1].split(',', 1)[0]
227 
228  """ Extract delimiter """
229  if '/delimiter=' in line.lower() and not '!' in line:
230  if 'comma' in line.lower():
231  delim = ',+'
232  elif 'space' in line.lower():
233  delim = '\s+'
234  elif 'tab' in line.lower():
235  delim = '\t+'
236  else:
237  raise Exception('Invalid delimiter detected in file: {:}. In line: {:}'.format(self.filename,line))
238  return
239 
240  """ Extract comments, but not history of metadata changes """
241  if '!' in line and not '!/' in line:
242  self.comments.append(line[1:])
243 
244  """ Check for required SeaBASS file header elements before parsing data matrix """
245  if '/end_header' in line.lower():
246  if not delim:
247  raise Exception('No valid /delimiter detected in file: {:}'.format(self.filename))
248  return
249 
250  if not self.missing:
251  raise Exception('No valid /missing value detected in file: {:}'.format(self.filename))
252  return
253 
254  if not _vars:
255  raise Exception('No /fields detected in file: {:}'.format(self.filename))
256  return
257 
258  if self.data_use_warning and not no_warn:
259  print('Warning: data_use_warning header is present in file: {:}. This file contains measurements collected under unique conditions. Use with caution and consult headers, file comments, and documentation for additional information. Use no_warn=True to suppress this message.'.format(self.filename))
260 
261  if mask_above_detection_limit and not no_warn:
262  if not self.adl:
263  print('Warning: No above_detection_limit in file: {:}. Unable to mask values as NaNs. Use no_warn=True to suppress this message.'.format(self.filename))
264 
265  if mask_below_detection_limit and not no_warn:
266  if not self.bdl:
267  print('Warning: No below_detection_limit in file: {:}. Unable to mask values as NaNs. Use no_warn=True to suppress this message.'.format(self.filename))
268 
269  end_header = True
270  continue
271 
272  """ Extract data after headers """
273  if end_header and line:
274  try:
275  for var,dat in zip(_vars,re.split(delim,line)):
276  if is_number(dat):
277  if is_int(dat):
278  dat = int(dat)
279  else:
280  dat = float(dat)
281 
282  if mask_above_detection_limit and self.adl != '':
283  if dat == float(self.adl):
284  dat = float('nan')
285 
286  if mask_below_detection_limit and self.bdl != '':
287  if dat == float(self.bdl):
288  dat = float('nan')
289 
290  if mask_missing and dat == self.missing:
291  dat = float('nan')
292 
293  self.data[var].append(dat)
294 
295  self.length = self.length + 1
296 
297  except Exception as e:
298  raise Exception('Unable to parse data from line in file: {:}. Error: {:}. In line: {:}'.format(self.filename,e,line))
299  return
300 
301  try:
302  self.variables = OrderedDict(zip(_vars,zip(_vars,_units)))
303 
304  except:
305  if not no_warn:
306  print('Warning: No valid units were detected in file: {:}. Use no_warn=True to suppress this message.'.format(self.filename))
307 
308  self.variables = OrderedDict(zip(_vars,_vars))
309 
310  return
311 
312 #==========================================================================================================================================
313 
314  def fd_datetime(self):
315  """ Convert date and time information from the file's data to a Python list of datetime objects.
316 
317  Returned data structure:
318  dt = a list of Python datetime objects
319 
320  Looks for these fields in this order:
321  date/time,
322  year/month/day/hour/minute/second,
323  year/month/day/time,
324  date/hour/minute/second,
325  date_time,
326  year/sdy/hour/minute/second,
327  year/sdy/time,
328  year/month/day/hour/minute,
329  date/hour/minute,
330  year/sdy/hour/minute,
331  year/month/day/hour,
332  date/hour,
333  year/sdy/hour,
334  year/month/day,
335  date,
336  year/sdy,
337  start_date/start_time (headers),
338  start_date (headers)
339  in the SELF Python structure.
340  """
341  dt = []
342 
343  if self.length == 0:
344  raise ValueError('readSB.data structure is missing for file: {:}'.format(self.filename))
345  return
346 
347  if 'date' in self.data and \
348  'time' in self.data:
349 
350  for d,t in zip([str(de) for de in self.data['date']],self.data['time']):
351  da = re.search("(\d{4})(\d{2})(\d{2})", d)
352  ti = re.search("(\d{1,2})\:(\d{2})\:(\d{2})", t)
353  try:
354  dt.append(datetime(int(da.group(1)), \
355  int(da.group(2)), \
356  int(da.group(3)), \
357  int(ti.group(1)), \
358  int(ti.group(2)), \
359  int(ti.group(3))))
360  except:
361  raise ValueError('date/time fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
362  return
363 
364  elif 'year' in self.data and \
365  'month' in self.data and \
366  'day' in self.data and \
367  'hour' in self.data and \
368  'minute' in self.data and \
369  'second' in self.data:
370 
371  for y,m,d,h,mn,s in zip(self.data['year'], self.data['month'], self.data['day'], self.data['hour'], self.data['minute'], self.data['second']):
372  try:
373  dt.append(datetime(int(y), \
374  int(m), \
375  int(d), \
376  int(h), \
377  int(mn), \
378  int(s)))
379  except:
380  raise ValueError('year/month/day/hour/minute/second fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
381  return
382 
383  elif 'year' in self.data and \
384  'month' in self.data and \
385  'day' in self.data and \
386  'time' in self.data:
387 
388  for y,m,d,t in zip(self.data['year'], self.data['month'], self.data['day'], self.data['time']):
389  ti = re.search("(\d{1,2})\:(\d{2})\:(\d{2})", t)
390  try:
391  dt.append(datetime(int(y), \
392  int(m), \
393  int(d), \
394  int(ti.group(1)), \
395  int(ti.group(2)), \
396  int(ti.group(3))))
397  except:
398  raise ValueError('year/month/day/time fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
399  return
400 
401  elif 'date' in self.data and \
402  'hour' in self.data and \
403  'minute' in self.data and \
404  'second' in self.data:
405 
406  for d,h,mn,s in zip([str(de) for de in self.data['date']], self.data['hour'], self.data['minute'], self.data['second']):
407  da = re.search("(\d{4})(\d{2})(\d{2})", d)
408  try:
409  dt.append(datetime(int(da.group(1)), \
410  int(da.group(2)), \
411  int(da.group(3)), \
412  int(h), \
413  int(mn), \
414  int(s)))
415  except:
416  raise ValueError('date/hour/minute/second fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
417  return
418 
419  elif 'date_time' in self.data:
420 
421  for i in self.data('date_time'):
422  da = re.search("(\d{4})-(\d{2})-(\d{2})\s(\d{1,2})\:(\d{2})\:(\d{2})", i)
423  try:
424  dt.append(datetime(int(da.group(1)), \
425  int(da.group(2)), \
426  int(da.group(3)), \
427  int(da.group(4)), \
428  int(da.group(5)), \
429  int(da.group(6))))
430  except:
431  raise ValueError('date_time field not formatted correctly; unable to parse in file: {:}'.format(self.filename))
432  return
433 
434  elif 'year' in self.data and \
435  'sdy' in self.data and \
436  'hour' in self.data and \
437  'minute' in self.data and \
438  'second' in self.data:
439 
440  for y,sdy,h,mn,s in zip(self.data['year'], self.data['sdy'], self.data['hour'], self.data['minute'], self.data['second']):
441  [m,d] = doy2mndy(y,sdy)
442  try:
443  dt.append(datetime(int(y), \
444  int(m), \
445  int(d), \
446  int(h), \
447  int(mn), \
448  int(s)))
449  except:
450  raise ValueError('year/sdy/hour/minute/second fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
451  return
452 
453  elif 'year' in self.data and \
454  'sdy' in self.data and \
455  'time' in self.data:
456 
457  for y,sdy,t in zip(self.data['year'], self.data['sdy'], self.data['time']):
458  [m,d] = doy2mndy(y,sdy)
459  ti = re.search("(\d{1,2})\:(\d{2})\:(\d{2})", t)
460  try:
461  dt.append(datetime(int(y), \
462  int(m), \
463  int(d), \
464  int(ti.group(1)), \
465  int(ti.group(2)), \
466  int(ti.group(3))))
467  except:
468  raise ValueError('year/sdy/time fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
469  return
470 
471  elif 'start_date' in self.headers and \
472  'time' in self.data:
473 
474  da = re.search("(\d{4})(\d{2})(\d{2})", self.headers['start_date'])
475  for t in self.data['time']:
476  ti = re.search("(\d{1,2})\:(\d{2})\:(\d{2})", t)
477  try:
478  dt.append(datetime(int(da.group(1)), \
479  int(da.group(2)), \
480  int(da.group(3)), \
481  int(ti.group(1)), \
482  int(ti.group(2)), \
483  int(ti.group(3))))
484  except:
485  raise ValueError('start_date header and time field not formatted correctly; unable to parse in file: {:}'.format(self.filename))
486  return
487 
488  elif 'start_date' in self.headers and \
489  'hour' in self.data and \
490  'minute' in self.data and \
491  'second' in self.data:
492 
493  da = re.search("(\d{4})(\d{2})(\d{2})", self.headers['start_date'])
494  for h,mn,s in zip(self.data['hour'], self.data['minute'], self.data['second']):
495  try:
496  dt.append(datetime(int(da.group(1)), \
497  int(da.group(2)), \
498  int(da.group(3)), \
499  int(h), \
500  int(mn), \
501  int(s)))
502  except:
503  raise ValueError('start_date header and hour/minute/second field not formatted correctly; unable to parse in file: {:}'.format(self.filename))
504  return
505 
506  elif 'year' in self.data and \
507  'month' in self.data and \
508  'day' in self.data and \
509  'hour' in self.data and \
510  'minute' in self.data:
511 
512  for y,m,d,h,mn in zip(self.data['year'], self.data['month'], self.data['day'], self.data['hour'], self.data['minute']):
513  try:
514  dt.append(datetime(int(y), \
515  int(m), \
516  int(d), \
517  int(h), \
518  int(mn), \
519  int(0)))
520  except:
521  raise ValueError('year/month/day/hour/minute fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
522  return
523 
524  elif 'date' in self.data and \
525  'hour' in self.data and \
526  'minute' in self.data:
527 
528  for d,h,mn in zip([str(de) for de in self.data['date']], self.data['hour'], self.data['minute']):
529  da = re.search("(\d{4})(\d{2})(\d{2})", d)
530  try:
531  dt.append(datetime(int(da.group(1)), \
532  int(da.group(2)), \
533  int(da.group(3)), \
534  int(h), \
535  int(mn), \
536  int(0)))
537  except:
538  raise ValueError('date/hour/minute fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
539  return
540 
541  elif 'year' in self.data and \
542  'sdy' in self.data and \
543  'hour' in self.data and \
544  'minute' in self.data:
545 
546  for y,sdy,h,mn in zip(self.data['year'], self.data['sdy'], self.data['hour'], self.data['minute']):
547  [m,d] = doy2mndy(y,sdy)
548  try:
549  dt.append(datetime(int(y), \
550  int(m), \
551  int(d), \
552  int(h), \
553  int(mn), \
554  int(0)))
555  except:
556  raise ValueError('year/sdy/hour/minute fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
557  return
558 
559  elif 'year' in self.data and \
560  'month' in self.data and \
561  'day' in self.data and \
562  'hour' in self.data:
563 
564  for y,m,d,h in zip(self.data['year'], self.data['month'], self.data['day'], self.data['hour']):
565  try:
566  dt.append(datetime(int(y), \
567  int(m), \
568  int(d), \
569  int(h), \
570  int(0), \
571  int(0)))
572  except:
573  raise ValueError('year/month/day/hour fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
574  return
575 
576  elif 'date' in self.data and \
577  'hour' in self.data:
578 
579  for d,h in zip([str(de) for de in self.data['date']], self.data['hour']):
580  da = re.search("(\d{4})(\d{2})(\d{2})", d)
581  try:
582  dt.append(datetime(int(da.group(1)), \
583  int(da.group(2)), \
584  int(da.group(3)), \
585  int(h), \
586  int(0), \
587  int(0)))
588  except:
589  raise ValueError('date/hour fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
590  return
591 
592  elif 'year' in self.data and \
593  'sdy' in self.data and \
594  'hour' in self.data:
595 
596  for y,sdy,h in zip(self.data['year'], self.data['sdy'], self.data['hour']):
597  [m,d] = doy2mndy(y,sdy)
598  try:
599  dt.append(datetime(int(y), \
600  int(m), \
601  int(d), \
602  int(h), \
603  int(0), \
604  int(0)))
605  except:
606  raise ValueError('year/sdy/hour fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
607  return
608 
609  elif 'year' in self.data and \
610  'month' in self.data and \
611  'day' in self.data:
612 
613  for y,m,d in zip(self.data['year'], self.data['month'], self.data['day']):
614  try:
615  dt.append(datetime(int(y), \
616  int(m), \
617  int(d), \
618  int(0), \
619  int(0), \
620  int(0)))
621  except:
622  raise ValueError('year/month/day fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
623  return
624 
625  elif 'date' in self.data:
626 
627  for d in zip([str(de) for de in self.data['date']]):
628  da = re.search("(\d{4})(\d{2})(\d{2})", d)
629  try:
630  dt.append(datetime(int(da.group(1)), \
631  int(da.group(2)), \
632  int(da.group(3)), \
633  int(0), \
634  int(0), \
635  int(0)))
636  except:
637  raise ValueError('date field not formatted correctly; unable to parse in file: {:}'.format(self.filename))
638  return
639 
640  elif 'year' in self.data and \
641  'sdy' in self.data:
642 
643  for y,sdy in zip(self.data['year'], self.data['sdy']):
644  [m,d] = doy2mndy(y,sdy)
645  try:
646  dt.append(datetime(int(y), \
647  int(m), \
648  int(d), \
649  int(0), \
650  int(0), \
651  int(0)))
652  except:
653  raise ValueError('year/sdy fields not formatted correctly; unable to parse in file: {:}'.format(self.filename))
654  return
655 
656  elif 'start_date' in self.headers and 'start_time' in self.headers:
657 
658  da = re.search("(\d{4})(\d{2})(\d{2})", self.headers['start_date'])
659  ti = re.search("(\d{1,2})\:(\d{2})\:(\d{2})\[(gmt|GMT)\]", self.headers['start_time'])
660  for i in range(self.length):
661  try:
662  dt.append(datetime(int(da.group(1)), \
663  int(da.group(2)), \
664  int(da.group(3)), \
665  int(ti.group(1)), \
666  int(ti.group(2)), \
667  int(ti.group(3))))
668  except:
669  raise ValueError('/start_date and /start_time headers not formatted correctly; unable to parse in file: {:}'.format(self.filename))
670  return
671 
672  elif 'start_date' in self.headers:
673 
674  da = re.search("(\d{4})(\d{2})(\d{2})", self.headers['start_date'])
675  for i in range(self.length):
676  try:
677  dt.append(datetime(int(da.group(1)), \
678  int(da.group(2)), \
679  int(da.group(3)), \
680  int(0), \
681  int(0), \
682  int(0)))
683  except:
684  raise ValueError('/start_date header not formatted correctly; unable to parse in file: {:}'.format(self.filename))
685  return
686 
687  else:
688  print('Warning: fd_datetime failed -- file must contain a valid date and time information')
689 
690  return(dt)
691 
692 #==========================================================================================================================================
693 
694  def addDataToOutput(self ,irow,var_name,units,var_value, overwrite):
695 
696  from copy import deepcopy
697 
698  #create empty column template for new var instantiation
699  if not self.empty_col:
700  for i in range(self.length):
701  self.empty_col.append(str(self.missing))
702 
703  #handle the case where irow is used to extend the data matrix/concatenate files together
704  if irow >= self.length:
705  drow = irow - self.length
706  for i in range(drow + 1):
707  self.length = self.length + 1
708  self.empty_col.append(str(self.missing))
709  for var in self.data:
710  self.data[var].append(str(self.missing))
711 
712  #check for valid inputs
713  if not var_value:
714  var_value = str(self.missing)
715  if not units:
716  units = 'none'
717 
718  #define fields, units, and data column, if needed
719  if var_name not in self.data:
720  self.headers['fields'] = self.headers['fields'] + ',' + var_name
721  try:
722  self.headers['units'] = self.headers['units'] + ',' + units.lower()
723  except:
724  print('Warning: no units found in SeaBASS file header')
725  self.data[var_name] = deepcopy(self.empty_col)
726 
727  #save data to column and row
728  if is_number(self.data[var_name][irow]):
729  if overwrite:
730  self.data[var_name][irow] = var_value
731  else:
732  if float(self.data[var_name][irow]) == self.missing:
733  self.data[var_name][irow] = var_value
734  else:
735  if overwrite:
736  self.data[var_name][irow] = var_value
737  else:
738  if str(self.missing) in self.data[var_name][irow]:
739  self.data[var_name][irow] = var_value
740 
741  return
742 
743 #==========================================================================================================================================
744 
745  def writeSBfile(self, ofile):
746 
747  """
748  writeSBfile writes out an SeaBASS file
749  given an output file name
750  syntax: SELF.writeSBfile(ofile)
751  """
752  from math import isnan
753 
754  fout = open(ofile,'w')
755 
756  fout.write('/begin_header\n')
757 
758  for header in self.headers:
759  fout.write('/' + header + '=' + self.headers[header] + '\n')
760 
761  for comment in self.comments:
762  fout.write('!' + comment + '\n')
763 
764  fout.write('/end_header\n')
765 
766  if 'comma' in self.headers['delimiter']:
767  delim = ','
768  elif 'space' in self.headers['delimiter']:
769  delim = ' '
770  elif 'tab' in self.headers['delimiter']:
771  delim = '\t'
772 
773  for i in range(self.length):
774  row_ls = []
775 
776  for var in self.data:
777 
778  if is_number(self.data[var][i]):
779  if float(self.data[var][i]) == float(self.missing) or isnan(float(self.data[var][i])):
780  row_ls.append(str(self.missing))
781  else:
782  row_ls.append(str(self.data[var][i]))
783  else:
784  if str(self.missing) in self.data[var][i] or 'nan' in self.data[var][i].lower():
785  row_ls.append(str(self.missing))
786  else:
787  row_ls.append(str(self.data[var][i]))
788 
789  fout.write(delim.join(row_ls) + '\n')
790 
791  fout.close()
792 
793  return
def addDataToOutput(self, irow, var_name, units, var_value, overwrite)
Definition: SB_support.py:694
def is_int(s)
Definition: SB_support.py:61
def writeSBfile(self, ofile)
Definition: SB_support.py:745
def __init__(self, filename, mask_missing=True, mask_above_detection_limit=True, mask_below_detection_limit=True, no_warn=False)
Definition: SB_support.py:115
def doy2mndy(yr, doy)
Definition: SB_support.py:77
const char * str
Definition: l1c_msi.cpp:35
def fd_datetime(self)
Definition: SB_support.py:314
def is_number(s)
Definition: SB_support.py:45