NASA Logo
Ocean Color Science Software

ocssw V2022
anc_utils.py
Go to the documentation of this file.
1 
2 import os
3 import sys
4 import gc
5 import re
6 import subprocess
7 import json
8 from datetime import datetime
9 from datetime import timedelta
10 
11 import xml.etree.ElementTree as ElementTree
12 from operator import sub
13 from collections import OrderedDict
14 
15 import seadasutils.MetaUtils as MetaUtils
16 import seadasutils.ProcUtils as ProcUtils
17 from seadasutils.timestamp_utils import goci_timestamp
18 from seadasutils.timestamp_utils import hawkeye_timestamp
19 from seadasutils.timestamp_utils import hico_timestamp
20 from seadasutils.timestamp_utils import meris_timestamp
21 from seadasutils.timestamp_utils import msi_timestamp
22 from seadasutils.timestamp_utils import ocm2_timestamp
23 from seadasutils.timestamp_utils import olci_timestamp
24 from seadasutils.timestamp_utils import etm_timestamp
25 from seadasutils.timestamp_utils import sgli_timestamp
26 from seadasutils.timestamp_utils import tm_timestamp
27 from seadasutils.timestamp_utils import l9_timestamp
28 from modis.modis_utils import modis_timestamp
29 from viirs.viirs_utils import viirs_timestamp
30 from seadasutils.aquarius_utils import aquarius_timestamp
31 
32 
33 import seadasutils.ancDB as db
34 #import modules.ancDBmysql as db
35 
36 
37 DEFAULT_ANC_DIR_TEXT = "$OCVARROOT"
38 
39 
40 class getanc:
41  """
42  utilities for ancillary file search
43  """
44 
45  def __init__(self, filename=None,
46  start=None,
47  stop=None,
48  ancdir=None,
49  ancdb='ancillary_data.db',
50  anc_file=None,
51  curdir=False,
52  atteph=False,
53  sensor=None,
54  opt_flag=None,
55  verbose=0,
56  printlist=True,
57  download=True,
58  timeout=60,
59  refreshDB=False,
60  use_filename=False):
61  self.filename = filename
62  self.start = start
63  self.stop = stop
64  self.ancdir = ancdir
65  self.ancdb = ancdb
66  self.anc_file=anc_file
67  self.curdir = curdir
68  self.opt_flag = opt_flag
69  self.atteph = atteph
70  self.dl = download
71  self.refreshDB = refreshDB
72  self.sensor = sensor
73  self.dirs = {}
74  self.files = {}
75  self.printlist = printlist
76  self.verbose = verbose
77  self.timeout = timeout
78  self.server_status = None
79  self.db_status = None
80  self.proctype = None
81  self.use_filename=use_filename
82  if self.atteph:
83  self.proctype = 'modisGEO'
84 
85  self.query_site = "oceandata.sci.gsfc.nasa.gov"
86  self.data_site = "oceandata.sci.gsfc.nasa.gov"
87 
88  def chk(self):
89  """
90  Check validity of inputs to
91  """
92 
93  if self.start is None and self.filename is None:
94  print("ERROR: No L1A_or_L1B_file or start time specified!")
95  sys.exit(32)
96 
97  if self.atteph:
98  if self.sensor is None and self.filename is None:
99  print("ERROR: No FILE or MISSION specified.")
100  sys.exit(1)
101  if self.sensor is not None and self.sensor != "modisa" and self.sensor != "modist" \
102  and self.sensor.lower() != "aqua" and self.sensor.lower() != "terra":
103  print("ERROR: Mission must be 'aqua', 'modisa', 'terra', or 'modist' ")
104  sys.exit(16)
105 
106  if self.curdir is True and self.ancdir is not None:
107  print("ERROR: The '--use-current' and '--ancdir' arguments cannot be used together.")
108  print(" Please use only one of these options.")
109  sys.exit(1)
110 
111  if self.start is not None:
112  if (len(self.start) != 13 and len(self.start) != 19) or int(self.start[0:4]) < 1978 or int(self.start[0:4]) > 2030:
113  print("ERROR: Start time must be in YYYYDDDHHMMSS or YYYY-MM-DDTHH:MM:SS format and YYYY is between 1978 and 2030.")
114  sys.exit(1)
115 
116  if self.stop is not None:
117  if (len(self.stop) != 13 and len(self.start) != 19) or int(self.stop[0:4]) < 1978 or int(self.stop[0:4]) > 2030:
118  print("ERROR: End time must be in YYYYDDDHHMMSS or YYYY-MM-DDTHH:MM:SS format and YYYY is between 1978 and 2030.")
119  sys.exit(1)
120 
121  @staticmethod
123  """
124  Extracts and returns the start time, start date, end time, and end date
125  from info. Returns a None value for any item not found.
126  """
127  starttime = None
128  stoptime = None
129  startdate = None
130  stopdate = None
131  for line in info[0].decode("utf-8").splitlines():
132  if line.find("Start_Time") != -1:
133  starttime = line.split('=')[1]
134  if line.find("End_Time") != -1:
135  stoptime = line.split('=')[1]
136  if line.find("Start_Date") != -1:
137  startdate = line.split('=')[1]
138  if line.find("End_Date") != -1:
139  stopdate = line.split('=')[1]
140  return starttime, startdate, stoptime, stopdate
141 
142  @staticmethod
143  def get_goci_time(goci_time_str):
144  month_dict = {'JAN': '01', 'FEB': '02', 'MAR': '03', 'APR': '04',
145  'MAY': '05', 'JUN': '06', 'JUL': '07', 'AUG': '08',
146  'SEP': '09', 'OCT': '10', 'NOV': '11', 'DEC': '12'}
147  time_str = goci_time_str[8:12] + '-' + \
148  month_dict[goci_time_str[4:7]] + '-' + \
149  goci_time_str[1:3] + 'T' + goci_time_str[13:15] + ':' + \
150  goci_time_str[16:18] + ':' + goci_time_str[19:21] + '.' + \
151  goci_time_str[22:25] + 'Z'
152  return time_str
153 
154  @staticmethod
156  data_elem = elem.find('Data')
157  value_elem = data_elem.find('DataFromFile')
158  return value_elem.text.strip()
159 
160  def get_start_end_info_from_xml(self, raw_xml):
161  """
162  Extracts and returns the start time, start date, end time, and end date
163  from the XML tree in raw_xml. Returns a None value for any item not
164  found.
165  """
166 
167  xml_root = ElementTree.fromstring(raw_xml)
168 
169  time_start_list = xml_root.findall('.//Attribute[@Name="time_coverage_start"]')
170  if len(time_start_list) > 0:
171  if len(time_start_list) > 1:
172  print("Encountered more than 1 time_coverage_start tag. Using 1st value.")
173  start = self.get_time_coverage_xml(time_start_list[0])
174  else:
175  time_start_list = xml_root.findall('.//Attribute[@Name="Scene Start time"]')
176  if len(time_start_list) > 1:
177  print("Encountered more than 1 Scene Start time tag. Using 1st value.")
178  start_str = self.get_time_coverage_xml(time_start_list[0])
179  start = self.get_goci_time(start_str)
180 
181  time_end_list = xml_root.findall('.//Attribute[@Name="time_coverage_end"]')
182  if len(time_end_list) > 0:
183  if len(time_end_list) > 1:
184  print("Encountered more than 1 time_coverage_end tag. Using 1st value.")
185  stop = self.get_time_coverage_xml(time_end_list[0])
186  else:
187  time_end_list = xml_root.findall('.//Attribute[@Name="Scene end time"]')
188  if len(time_end_list) > 1:
189  print("Encountered more than 1 Scene end time tag. Using 1st value.")
190  stop_str = self.get_time_coverage_xml(time_end_list[0])
191  stop = self.get_goci_time(stop_str)
192  return start, stop
193 
194  def setup(self):
195  """
196  Set up the basics
197  """
198  # global stopdate, stoptime, startdate, starttime
199 
200  # set l2gen parameter filename
201  if self.filename is None:
202  if len(self.start) == 13:
203  start_obj = datetime.strptime(self.start, '%Y%j%H%M%S')
204  self.start = datetime.strftime(start_obj, '%Y-%m-%dT%H:%M:%S')
205  if self.stop is not None:
206  stop_obj = datetime.strptime(self.stop, '%Y%j%H%M%S')
207  self.stop = datetime.strftime(stop_obj, '%Y-%m-%dT%H:%M:%S')
208  if self.anc_file is None:
209  self.base = self.start
210  else:
211  self.base = self.anc_file.replace(".anc", "")
212  self.server_file = self.base + ".anc.server"
213 
214  if self.atteph:
215  self.anc_file = self.base + ".atteph"
216  else:
217  self.anc_file = self.base + ".anc"
218  elif self.use_filename:
219  if self.anc_file is None:
220  self.base = os.path.basename(self.filename)
221  self.server_file = "{0:>s}.anc.server".format('.'.join(self.base.split('.')[0:-1]))
222  if self.atteph:
223  self.anc_file = "{0:>s}.atteph".format('.'.join(self.base.split('.')[0:-1]))
224  else:
225  self.anc_file = "{0:>s}.anc".format('.'.join(self.base.split('.')[0:-1]))
226  else:
227  self.base = self.anc_file.replace(".anc", "")
228  self.server_file = self.base + ".anc.server"
229 
230  if self.atteph:
231  self.anc_file = '.'.join([self.base, 'atteph'])
232  else:
233  self.anc_file = '.'.join([self.base, 'anc'])
234 
235  if self.server_file == '.anc.server':
236  self.server_file = self.filename + self.server_file
237  self.anc_file = self.filename + self.anc_file
238  else:
239  if self.anc_file is None:
240  self.base = os.path.basename(self.filename)
241  self.server_file = "{0:>s}.anc.server".format('.'.join(self.base.split('.')[0:-1]))
242  else:
243  self.base = self.anc_file.replace(".anc", "")
244  self.server_file = self.base + ".anc.server"
245 
246  if self.atteph:
247  self.anc_file = '.'.join([self.base, 'atteph'])
248  else:
249  self.anc_file = '.'.join([self.base, 'anc'])
250 
251  if self.server_file == '.anc.server':
252  self.server_file = self.filename + self.server_file
253  self.anc_file = self.filename + self.anc_file
254 
255  # Check if start time specified.. if not, obtain from HDF header or if the
256  # file doesn't exist, obtain start time from filename
257  if self.start is None:
258  # Check for existence of ifile, and if it doesn't exist assume the
259  # user wants to use this script without an actual input file, but
260  # instead an OBPG formatted filename that indicates the start time.
261  if not os.path.exists(self.filename):
262  if self.sensor:
263  print("*** WARNING: Input file doesn't exist! Parsing filename for start time and setting")
264  print("*** end time to 5 minutes later for MODIS and 15 minutes later for other sensors.")
265  if len(self.base) < 14 or int(self.base[1:5]) < 1978 or int(self.base[1:5]) > 2030:
266  print("ERROR: Filename must be in XYYYYDDDHHMMSS format where X is the")
267  print("sensor letter and YYYY is between 1978 and 2030.")
268  sys.exit(1)
269  else:
270  self.start = self.base[1:14]
271  else:
272  print("*** ERROR: Input file doesn't exist and mission not set...bailing out...")
273  sys.exit(1)
274 
275  else:
276  # Determine start/end times from HDF file
277  # for l1info subsample every 250 lines
278  if self.verbose:
279  print("Determining pass start and end times...")
280  senchk = ProcUtils.check_sensor(self.filename)
281 
282  if re.search('(Aqua|Terra)', senchk):
283  # if self.mission == "A" or self.mission == "T":
284  self.start, self.stop, self.sensor = modis_timestamp(self.filename)
285  elif senchk.find("viirs|JPSS-1|JPSS-2|Suomi_NPP") == 0:
286  self.start, self.stop, self.sensor = viirs_timestamp(self.filename)
287  elif senchk.find("aquarius") == 0:
288  self.start, self.stop, self.sensor = aquarius_timestamp(self.filename)
289  elif re.search('goci', senchk):
290  self.start, self.stop, self.sensor = goci_timestamp(self.filename)
291  elif re.search('hawkeye', senchk):
292  self.start, self.stop, self.sensor = hawkeye_timestamp(self.filename)
293  elif senchk.find("hico") == 0:
294  self.start, self.stop, self.sensor = hico_timestamp(self.filename)
295  elif re.search('meris', senchk):
296  self.start, self.stop, self.sensor = meris_timestamp(self.filename)
297  elif re.search('(S2A|S2B)', senchk):
298  self.start, self.stop, self.sensor = msi_timestamp(self.filename)
299  elif re.search('ocm2', senchk):
300  self.start, self.stop, self.sensor = ocm2_timestamp(self.filename)
301  elif re.search('ETM', senchk):
302  self.start, self.stop, self.sensor = etm_timestamp(self.filename)
303  elif re.search('(3A|3B)', senchk):
304  self.start, self.stop, self.sensor = olci_timestamp(self.filename)
305  elif re.search('sgli', senchk):
306  self.start, self.stop, self.sensor = sgli_timestamp(self.filename)
307  elif re.search('tm', senchk):
308  self.start, self.stop, self.sensor = tm_timestamp(self.filename)
309  elif re.search('L9', senchk):
310  self.start, self.stop, self.sensor = l9_timestamp(self.filename)
311  else:
312  if self.sensor is None:
313  self.sensor = senchk
314  mime_data = MetaUtils.get_mime_data(self.filename)
315  if MetaUtils.is_netcdf4(mime_data):
316  metadata = MetaUtils.dump_metadata(self.filename)
317  starttime, stoptime = self.get_start_end_info_from_xml(metadata)
318  starttime = starttime.strip('"')
319  stoptime = stoptime.strip('"')
320  starttime = starttime.strip("'")
321  stoptime = stoptime.strip("'")
322  if starttime.find('T') != -1:
323  self.start = starttime[0:19]
324  # self.start = ProcUtils.date_convert(starttime, 't', 'j')
325  else:
326  self.start = starttime[0:19]
327  # self.start = ProcUtils.date_convert(starttime, 'h', 'j')
328  if stoptime.find('T') != -1:
329  self.stop = stoptime[0:19]
330  # self.stop = ProcUtils.date_convert(stoptime, 't', 'j')
331  else:
332  self.stop = stoptime[0:19]
333  # self.stop = ProcUtils.date_convert(stoptime, 'h', 'j')
334  pass
335  else:
336  infocmd = [os.path.join(self.dirs['bin'], 'l1info'), '-s', '-i 250', self.filename]
337  l1info = subprocess.Popen(infocmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
338  info = l1info.communicate()
339  (starttime, startdate, stoptime, stopdate) = self.get_start_end_info(info)
340  if not starttime or not startdate or not stoptime or not stopdate:
341  err_msg = 'ERROR: For ' + self.base + ' could not determine: '
342  if not starttime:
343  err_msg = err_msg + ' start time'
344  if not startdate:
345  if not starttime:
346  err_msg = err_msg + ', start date'
347  else:
348  err_msg = err_msg + ' start date'
349  if not stoptime:
350  if not starttime or not startdate:
351  err_msg = err_msg + ', stoptime'
352  else:
353  err_msg = err_msg + ' stop time'
354  if not stopdate:
355  if not starttime or not startdate or not stoptime:
356  err_msg = err_msg + ', stop date'
357  else:
358  err_msg = err_msg + ' stop date'
359  err_msg = err_msg + '. Exiting.'
360  print(err_msg)
361  if info[1]:
362  print("l1info reported the following error:")
363  print(' {0}'.format(info[1]))
364  sys.exit(1)
365  else:
366  # self.start = ProcUtils.date_convert(startdate + ' ' + starttime, 'h', 'j')
367  # self.stop = ProcUtils.date_convert(stopdate + ' ' + stoptime, 'h', 'j')
368  self.start = startdate + 'T' + starttime[0:8]
369  self.stop = stopdate + 'T' + stoptime[0:8]
370 
371  if self.filename and not self.sensor and not self.use_filename:
372  # Make sure sensor is set (JIRA Ticket #1012)
373  self.sensor = ProcUtils.check_sensor(self.filename)
374 
375  if self.verbose:
376  print()
377  print("Input file: " + str(self.filename))
378  print("Sensor : " + str(self.sensor))
379  print("Start time: " + str(self.start))
380  print("End time : " + str(self.stop))
381  print()
382 
383  def set_opt_flag(self, key, off=False):
384  """
385  set up the opt_flag for display_ancillary_data (type=anc)
386  opt_flag values:
387  0 - just the basics, MET/OZONE
388  1 - include OISST
389  2 - include NO2
390  4 - include ICE
391  """
392  optkey = {'sst': 1, 'no2': 2, 'ice': 4}
393 
394  if off:
395  self.opt_flag = self.opt_flag - optkey[key]
396  else:
397  self.opt_flag = self.opt_flag + optkey[key]
398 
399  def finddb(self):
400  """
401  Checks local db for anc files.
402  """
403  print(self.ancdb)
404  if len(os.path.dirname(self.ancdb)):
405  self.dirs['log'] = os.path.dirname(self.ancdb)
406  self.ancdb = os.path.basename(self.ancdb)
407 
408  if not os.path.exists(self.dirs['log']):
409  self.ancdb = os.path.basename(self.ancdb)
410  print('''Directory %s does not exist.
411 Using current working directory for storing the ancillary database file: %s''' % (self.dirs['log'], self.ancdb))
412  self.dirs['log'] = self.dirs['run']
413 
414  self.ancdb = os.path.join(self.dirs['log'], self.ancdb)
415 
416  if not os.path.exists(self.ancdb):
417  return 0
418 
419  ancdatabase = db.ancDB(dbfile=self.ancdb)
420  if not os.path.getsize(self.ancdb):
421  if self.verbose:
422  print("Creating database: %s " % self.ancdb)
423  ancdatabase.openDB()
424  ancdatabase.create_db()
425  else:
426  ancdatabase.openDB()
427  if self.verbose:
428  print("Searching database: %s " % self.ancdb)
429 
430  if self.filename:
431  filekey = os.path.basename(self.filename)
432  if (re.search('manifest.safe', self.filename) or re.search('xfdumanifest.xml', self.filename)):
433  # filekey = *.SAFE/maifest.safe
434  filekey = os.path.join(os.path.basename(os.path.dirname(self.filename)), filekey)
435  else:
436  filekey = None
437  if self.start and len(self.start) == 13:
438  start_obj = datetime.strptime(self.start, '%Y%j%H%M%S')
439  self.start = datetime.strftime(start_obj, '%Y-%m-%dT%H:%M:%S')
440  if self.stop is not None:
441  stop_obj = datetime.strptime(self.stop, '%Y%j%H%M%S')
442  self.stop = datetime.strftime(stop_obj, '%Y-%m-%dT%H:%M:%S')
443  status = ancdatabase.check_file(filekey,starttime=self.start)
444  if status:
445  if not self.refreshDB:
446  self.files = ancdatabase.get_ancfiles(filekey, self.atteph, starttime=self.start)
447  if self.curdir:
448  for anckey in list(self.files.keys()):
449  self.files[anckey] = os.path.basename(self.files[anckey])
450  self.db_status = ancdatabase.get_status(filekey, self.atteph, starttime=self.start)
451  self.start, self.stop = ancdatabase.get_filetime(filekey, starttime=self.start)
452  else:
453  ancdatabase.delete_record(filekey, starttime=self.start)
454 
455  ancdatabase.closeDB()
456 
457  if status and self.db_status:
458  if not self.refreshDB:
459  if self.db_status > 0:
460  print("Warning! Non-optimal data exist in local repository.")
461  print("Consider re-running with the --refreshDB option to check for optimal ancillary files")
462  return 1
463  else:
464  return 0
465  else:
466  return 0
467 
468  def findweb(self):
469  """
470  Execute the display_ancillary_files search and populate the locate cache database
471  """
472 
473  # dlstat = 0
474 
475  with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'missionID.json'), 'r') as msn_file:
476  msn = json.load(msn_file)
477 
478  # msn = {"seawifs": "6", "modisa": "7", "aqua": "7", "modist": "8", "terra": "8", "octs": "9",
479  # "czcs": "11", "suomi-npp": "14", "aquarius": "15", "meris": "19", "ocm2": "21", "hico" : "25",
480  # "goci": "27", "oli": "28", "3a": "29", "jpss-1": "33","hawkeye": "35", "3b": "36"}
481 
482  ProcUtils.remove(self.server_file)
483 
484  # Query the OBPG server for the ancillary file list
485  opt_flag = str(self.opt_flag)
486  anctype = 'anc_data_api'
487  # if self.atteph:
488  # opt_flag = ''
489  # anctype = 'atteph_test'
490 
491  if self.sensor == 'aquarius':
492  opt_flag = ''
493 
494  msnchar = '0'
495  if str(self.sensor).lower() in msn:
496  # msnchar = msn[str(self.sensor).lower()]
497  msnchar = msn[str(self.sensor.lower())]
498  elif self.sensor and self.sensor.isdigit():
499  msnchar = self.sensor
500 
501  if self.stop is None and not self.use_filename:
502  start_obj = datetime.strptime(self.start, '%Y-%m-%dT%H:%M:%S')
503  time_change = timedelta(minutes=5)
504  stop_obj = start_obj + time_change
505  self.stop = datetime.strftime(stop_obj,'%Y-%m-%dT%H:%M:%S')
506  anc_str = '?&m=' + msnchar + '&s=' + self.start + '&e=' + self.stop + '&missing_tags=1'
507  dlstat = ProcUtils.httpdl(self.query_site, '/'.join(['/api', anctype, anc_str]),
508  os.path.abspath(os.path.dirname(self.server_file)),
509  outputfilename=self.server_file,
510  timeout=self.timeout,
511  verbose=self.verbose
512  )
513  elif self.use_filename:
514  anc_str = '?filename=' + os.path.basename(self.filename) + '&missing_tags=1'
515  dlstat = ProcUtils.httpdl(self.query_site,
516  '/'.join(['/api', anctype, anc_str]),
517  os.path.abspath(os.path.dirname(self.server_file)),
518  outputfilename=self.server_file,
519  timeout=self.timeout,
520  verbose=self.verbose
521  )
522  else:
523  anc_str = '?&m=' + msnchar + '&s=' + self.start + '&e=' + self.stop + '&missing_tags=1'
524  dlstat = ProcUtils.httpdl(self.query_site,
525  '/'.join(['/api', anctype, anc_str]),
526  os.path.abspath(os.path.dirname(self.server_file)),
527  outputfilename=self.server_file,
528  timeout=self.timeout,
529  verbose=self.verbose
530  )
531  gc.collect()
532 
533  if dlstat:
534  print("Error retrieving ancillary file list")
535  sys.exit(dlstat)
536 
537  with open(self.server_file, 'r') as data_file:
538  results = json.load(data_file)
539  self.db_status = int(results['status'])
540 
541  if self.db_status == 128:
542  ProcUtils.remove(self.anc_file)
543  print("The time duration provided is longer than 2 hours. please try with a shorter duration." )
544  print("No parameter file created.")
545  ProcUtils.remove(self.server_file)
546  sys.exit(128)
547 
548  if self.db_status == 64:
549  ProcUtils.remove(self.anc_file)
550  print("Stop time is earlier than start time." )
551  print("No parameter file created.")
552  ProcUtils.remove(self.server_file)
553  sys.exit(64)
554 
555  self.db_status = 0
556 
557  for f in results['files']:
558  if (len(str(f[1]))) == 0:
559  if not self.atteph:
560  if re.search('MET', f[0]):
561  self.db_status = self.db_status | 1
562  if re.search('OZONE', f[0]):
563  self.db_status = self.db_status | 2
564  if re.search('sst', f[0]):
565  self.db_status = self.db_status | 4
566  if re.search('no2', f[0]):
567  self.db_status = self.db_status | 8
568  if re.search('ice', f[0]):
569  self.db_status = self.db_status | 16
570  if self.atteph:
571  if re.search('ATT', f[0]):
572  self.db_status = self.db_status | 4
573  if re.search('EPH', f[0]):
574  self.db_status = self.db_status | 8
575  else:
576  if self.atteph:
577  if re.search('ATT|EPH', f[0]):
578  self.files[str(f[0]).lower()] = str(f[1])
579  if (f[2] != 1):
580  if re.search('ATT', f[0]):
581  self.db_status = self.db_status | 1
582  if re.search('EPH', f[0]):
583  self.db_status = self.db_status | 2
584  else:
585  if re.search('MET|OZONE|sst|ice|AER|GEO', f[0]):
586  self.files[str(f[0]).lower()] = str(f[1])
587  # self.db_status = int(results['status'])
588 
589  # FOR MET/OZONE:
590  # For each anc type, DB returns either a zero status if all optimal files are
591  # found, or different error statuses if not. However, 3 MET or OZONE files can be
592  # returned along with an error status meaning there were one or more missing
593  # files that were then filled with the file(s) found, and so though perhaps
594  # better than climatology it's still not optimal. Therefore check for cases
595  # where all 3 MET/ozone files are returned but status is negative and then
596  # warn the user there might be more files to come and they should consider
597  # reprocessing at a later date.
598  #
599  # DB return status bitwise values:
600  # -all bits off means all is well in the world
601  # -bit 0 = 1 - missing one or more MET
602  # -bit 1 = 1 - missing one or more OZONE
603  # -bit 2 = 1 - missing SST
604  # -bit 3 = 1 - missing NO2
605  # -bit 4 = 1 - missing ICE
606 
607  # FOR ATT/EPH:
608  # all bits off means all is well in the world
609  # -bit 0 = 1 - predicted attitude selected
610  # -bit 1 = 1 - predicted ephemeris selected
611  # -bit 2 = 1 - no attitude found
612  # -bit 3 = 1 - no ephemeris found
613 
614  if self.server_status == 1 or dlstat or self.db_status is None:
615  print("ERROR: The display_ancillary_files.cgi script encountered an error and returned the following text:")
616  print()
617  ProcUtils.cat(self.server_file)
618  sys.exit(99)
619 
620  if self.db_status == 31 and not self.atteph:
621  ProcUtils.remove(self.anc_file)
622  print("No ancillary files currently exist that correspond to the start time " + self.start)
623  print("No parameter file created (l2gen defaults to the climatologies).")
624  ProcUtils.remove(self.server_file)
625  sys.exit(31)
626 
627  if self.db_status == 12 and self.atteph:
628  ProcUtils.remove(self.anc_file)
629  print("No att/eph files currently exist that correspond to the start time " + self.start)
630  ProcUtils.remove(self.server_file)
631  sys.exit(12)
632 
633  # extra checks
634  for f in (list(self.files.keys())):
635  if not len(self.files[f]):
636  print("ERROR: display_ancillary_files.cgi script returned blank entry for %s. Exiting." % f)
637  sys.exit(99)
638 
639  ancdatabase = db.ancDB(dbfile=self.ancdb)
640 
641  if not os.path.exists(ancdatabase.dbfile) or os.path.getsize(ancdatabase.dbfile) == 0:
642  ancdatabase.openDB()
643  ancdatabase.create_db()
644  else:
645  ancdatabase.openDB()
646 
647  missing = []
648 
649  for anctype in self.files:
650  if self.files[anctype] == 'missing':
651  missing.append(anctype)
652  continue
653  if (self.filename and self.dl) or (self.start and self.dl):
654  path = self.dirs['anc']
655  if not self.curdir:
656  year, day = self.yearday(self.files[anctype])
657  path = os.path.join(path, year, day)
658 
659  if self.filename:
660  filekey = os.path.basename(self.filename)
661  if (re.search('manifest.safe', self.filename) or re.search('xfdumanifest.xml', self.filename)):
662  # filekey = *.SAFE/maifest.safe
663  filekey = os.path.join(os.path.basename(os.path.dirname(self.filename)), filekey)
664  else:
665  filekey = None
666  ancdatabase.insert_record(satfile=filekey, starttime=self.start, stoptime=self.stop, anctype=anctype,
667  ancfile=self.files[anctype], ancpath=path, dbstat=self.db_status,
668  atteph=self.atteph)
669 
670  ancdatabase.closeDB()
671  # remove missing items
672  for anctype in missing:
673  self.files.__delitem__(anctype)
674 
675  def yearday(self, ancfile):
676  if ancfile.startswith('RIM_'):
677  ymd = ancfile.split('_')[2]
678  dt = datetime.strptime(ymd, '%Y%m%d')
679  year = dt.strftime('%Y')
680  day = dt.strftime('%j')
681  return year, day
682 
683  if ancfile.startswith('MERRA'):
684  ymd = ancfile.split('.')[4]
685  dt = datetime.strptime(ymd, '%Y%m%d')
686  year = dt.strftime('%Y')
687  day = dt.strftime('%j')
688  return year, day
689 
690  # For YYYYmmddT or YYmmddHHMMSS
691  if re.search('[\d]{8}T', ancfile) or re.search('[\d]{14}', ancfile):
692  ymd = re.search('[\d]{8}', ancfile).group()
693  dt = datetime.strptime(ymd, '%Y%m%d')
694  year = dt.strftime('%Y')
695  day = dt.strftime('%j')
696  return year, day
697  # For YYYYDDDHHMMSS
698  elif re.search('[\d]{13}', ancfile):
699  ymd = re.search('[\d]{7}', ancfile).group()
700  year = ymd[0:4]
701  day = ymd[4:7]
702  return year, day
703  # For YYYYmmddHHMM
704  elif re.search('[\d]{12}', ancfile):
705  ymd = re.search('[\d]{8}', ancfile).group()
706  dt = datetime.strptime(ymd, '%Y%m%d')
707  year = dt.strftime('%Y')
708  day = dt.strftime('%j')
709  return year, day
710  # For YYYYDDDHHMM
711  elif re.search('[\d]{11}', ancfile):
712  ymd = re.search('[\d]{7}', ancfile).group()
713  year = ymd[0:4]
714  day = ymd[4:7]
715  return year, day
716  # For YYYYmmddHH
717  elif re.search('[\d]{10}', ancfile):
718  ymd = re.search('[\d]{8}', ancfile).group()
719  dt = datetime.strptime(ymd, '%Y%m%d')
720  year = dt.strftime('%Y')
721  day = dt.strftime('%j')
722  return year, day
723  # For YYYYDDDHH
724  elif re.search('[\d]{9}', ancfile):
725  ymd = re.search('[\d]{7}', ancfile).group()
726  year = ymd[0:4]
727  day = ymd[4:7]
728  return year, day
729  # For YYYYmmdd
730  elif re.search('[\d]{8}', ancfile):
731  ymd = re.search('[\d]{8}', ancfile).group()
732  dt = datetime.strptime(ymd, '%Y%m%d')
733  year = dt.strftime('%Y')
734  day = dt.strftime('%j')
735  return year, day
736  # For YYYYDDD
737  elif re.search('[\d]{7}', ancfile):
738  ymd = re.search('[\d]{7}', ancfile).group()
739  year = ymd[0:4]
740  day = ymd[4:7]
741  return year, day
742 
743  if ancfile.startswith('GMAO'):
744  ymd = ancfile.split('.')[1][0:8]
745  dt = datetime.strptime(ymd, '%Y%m%d')
746  year = dt.strftime('%Y')
747  day = dt.strftime('%j')
748  return year, day
749 
750  if ancfile[:1].isdigit():
751  ymd = ancfile[0:8]
752  dt = datetime.strptime(ymd, '%Y%m%d')
753  year = dt.strftime('%Y')
754  day = dt.strftime('%j')
755  return year, day
756 
757  if ancfile.startswith('SIF'):
758  yyyyddd = ancfile.split('.')[0]
759  offset = 3
760  elif ancfile.startswith('PERT_'):
761  yyyyddd = ancfile.split('_')[2]
762  offset = 0
763  elif self.atteph and not re.search(".(att|eph)$", ancfile):
764  yyyyddd = ancfile.split('.')[1]
765  offset = 1
766  else:
767  yyyyddd = ancfile
768  offset = 1 # skip only 1st char
769 
770  year = yyyyddd[offset:offset + 4]
771  day = yyyyddd[offset + 4:offset + 7]
772  return year, day
773 
774  def locate(self, forcedl=False):
775  """
776  Find the files on the local system or download from OBPG
777  """
778 
779  FILES = []
780  for f in (list(self.files.keys())):
781  if self.atteph:
782  if re.search('scat|atm|met|ozone|file|geo|aer', f):
783  continue
784  else:
785  if re.search('att|eph', f):
786  continue
787  FILES.append(os.path.basename(self.files[f]))
788 
789  dl_msg = 1
790 
791  for FILE in list(OrderedDict.fromkeys(FILES)):
792  year, day = self.yearday(FILE)
793 
794  # First check hard disk...unless forcedl is set
795 
796  if self.curdir:
797  self.dirs['path'] = '.' # self.dirs['anc']
798  if os.path.exists(FILE) and forcedl is False:
799  download = 0
800  if self.verbose:
801  print(" Found: %s" % FILE)
802  else:
803  download = 1
804  else:
805  ancdir = self.dirs['anc']
806 
807  self.dirs['path'] = os.path.join(ancdir, year, day)
808  if os.path.exists(os.path.join(ancdir, year, day, FILE)) and forcedl is False:
809  download = 0
810  if self.verbose:
811  print(" Found: %s/%s" % (self.dirs['path'], FILE))
812  else:
813  download = 1
814 
815  # Not on hard disk, download the file
816 
817  if download == 1:
818  if self.dl is False:
819  if dl_msg == 1:
820  if self.verbose:
821  print("Downloads disabled. The following missing file(s) will not be downloaded:")
822  dl_msg = 0
823  if self.verbose:
824  print(" " + FILE)
825  else:
826 
827  if self.verbose:
828  print("Downloading '" + FILE + "' to " + self.dirs['path'])
829  status = ProcUtils.httpdl(self.data_site, ''.join(['/ob/getfile/', FILE]),
830  self.dirs['path'], timeout=self.timeout, uncompress=True,force_download=forcedl,
831  verbose=self.verbose)
832  gc.collect()
833  if status:
834  if status == 304:
835  if self.verbose:
836  print("%s is not newer than local copy, skipping download" % FILE)
837  elif status == 401:
838  print("*** ERROR: Authentication Failure retrieving:")
839  print("*** " + '/'.join([self.data_site, 'ob/getfile', FILE]))
840  print("*** Please check that your ~/.netrc file is setup correctly and has proper permissions.")
841  print("***")
842  print("*** see: https://oceancolor.gsfc.nasa.gov/data/download_methods/")
843  print("***\n")
844  else:
845  print("*** ERROR: The HTTP transfer failed with status code " + str(status) + ".")
846  print("*** Please check your network connection and for the existence of the remote file:")
847  print("*** " + '/'.join([self.data_site, 'ob/getfile', FILE]))
848  print("***")
849  print("*** Also check to make sure you have write permissions under the directory:")
850  print("*** " + self.dirs['path'])
851  print()
852 
853  if status != 304:
854  ProcUtils.remove(os.path.join(self.dirs['path'], FILE))
855  ProcUtils.remove(self.server_file)
856  sys.exit(1)
857 
858  for f in (list(self.files.keys())):
859  if self.atteph:
860  if re.search('met|ozone|file|aer|geo', f):
861  continue
862  else:
863  if re.search('att|eph', f):
864  continue
865  if FILE == self.files[f]:
866  self.files[f] = os.path.join(self.dirs['path'], FILE)
867 
868  def write_anc_par(self):
869  """
870  create the .anc parameter file
871  """
872  ProcUtils.remove(self.anc_file)
873 
874  NONOPT = ""
875  if not self.atteph:
876 
877  if self.sensor == 'aquarius':
878  inputs = {'MET': {'bitval': 1, 'required': ['met1', 'met2', 'atm1', 'atm2']},
879  'SST': {'bitval': 4, 'required': ['sstfile1', 'sstfile2']},
880  'SeaIce': {'bitval': 16, 'required': ['icefile1', 'icefile2']},
881  'Salinity': {'bitval': 32, 'required': ['sssfile1', 'sssfile2']},
882  'XRAY': {'bitval': 64, 'required': ['xrayfile1']},
883  # 'SCAT': {'bitval': 128, 'required': ['scat']},
884  'TEC': {'bitval': 256, 'required': ['tecfile']},
885  'SWH': {'bitval': 512, 'required': ['swhfile1', 'swhfile2']},
886  'Frozen': {'bitval': 1024, 'required': ['frozenfile1', 'frozenfile2']},
887  'GEOS': {'bitval': 2048, 'required': ['geosfile']},
888  'ARGOS': {'bitval': 4096, 'required': ['argosfile1', 'argosfile2']},
889  'SIF': {'bitval': 8192, 'required': ['sif']},
890  'PERT': {'bitval': 16384, 'required': ['pert']},
891  'Matchup': {'bitval': 32768, 'required': ['sssmatchup']},
892  'Rainfall': {'bitval': 65536, 'required': ['rim_file']}}
893  for anc in inputs:
894  if self.db_status & inputs[anc]['bitval']:
895  NONOPT = " ".join([NONOPT, anc])
896  else:
897  for ancfile in (inputs[anc]['required']):
898  if ancfile not in self.files:
899  NONOPT = " ".join([NONOPT, anc])
900  print('*** WARNING: No optimal {0} files found.'.format(ancfile))
901  break
902 
903  else: # not aquarius
904 
905  if self.db_status & 1:
906  NONOPT = " ".join([NONOPT, 'MET'])
907  else:
908  for key in (['met1', 'met2', 'met3']):
909  if key not in self.files:
910  NONOPT = " ".join([NONOPT, 'MET'])
911  print("*** WARNING: No optimal MET files found.")
912  break
913 
914  if self.db_status & 2:
915  NONOPT = " ".join([NONOPT, 'OZONE'])
916  else:
917  for key in (['ozone1', 'ozone2', 'ozone3']):
918  if key not in self.files:
919  NONOPT = " ".join([NONOPT, 'OZONE'])
920  print("*** WARNING: No optimal OZONE files found.")
921  break
922 
923  if self.opt_flag & 1 and ('sstfile' not in self.files or (self.db_status & 4)):
924  NONOPT = " ".join([NONOPT, 'SST'])
925  print("*** WARNING: No optimal SST files found.")
926 
927  if self.opt_flag & 2 and ('no2file' not in self.files or (self.db_status & 8)):
928  NONOPT = " ".join([NONOPT, 'NO2'])
929  print("*** WARNING: No optimal NO2 files found.")
930 
931  if self.opt_flag & 4 and ('icefile' not in self.files or (self.db_status & 16)):
932  NONOPT = " ".join([NONOPT, 'Sea Ice'])
933  print("*** WARNING: No optimal ICE files found.")
934 
935  ancpar = open(self.anc_file, 'w')
936 
937  for key in sorted(self.files.keys()):
938  if self.atteph:
939  if key.find('att') != -1 or key.find('eph') != -1:
940  ancpar.write('='.join([key, self.files[key]]) + '\n')
941  else:
942  if key.find('att') == -1 and key.find('eph') == -1:
943  ancpar.write('='.join([key, self.files[key]]) + '\n')
944 
945  ancpar.close()
946 
947  if self.verbose:
948  if self.atteph:
949  if self.dl:
950  print("All required attitude and ephemeris files successfully determined and downloaded.")
951  else:
952  print("All required attitude and ephemeris files successfully determined.")
953  else:
954  print()
955  print("Created '" + self.anc_file + "' l2gen parameter text file:\n")
956 
957  if self.verbose or self.printlist:
958  ProcUtils.cat(self.anc_file)
959 
960  if len(NONOPT):
961  if self.db_status == 31:
962  print("No optimal ancillary files were found.")
963  print("No parameter file was created (l2gen defaults to the climatological ancillary data).")
964  print("Exiting.")
965  ProcUtils.remove(self.server_file)
966  else:
967  print()
968  print("*** WARNING: The following ancillary data types were missing or are not optimal: " + NONOPT)
969  if self.db_status == 3:
970  print("*** Beware that certain MET and OZONE files just chosen by this program are not optimal.")
971  print("*** For near real-time processing the remaining files may become available soon.")
972  elif self.db_status == 1:
973  print("*** Beware that certain MET files just chosen by this program are not optimal.")
974  print("*** For near real-time processing the remaining files may become available soon.")
975  elif self.db_status == 2:
976  print("*** Beware that certain OZONE files just chosen by this program are not optimal.")
977  print("*** For near real-time processing the remaining files may become available soon.")
978  else:
979  if self.verbose:
980  if self.dl:
981  print()
982  print("- All optimal ancillary data files were determined and downloaded. -")
983  else:
984  print()
985  print("- All optimal ancillary data files were determined. -")
986 
987  def cleanup(self):
988  """
989  remove the temporary 'server' file and adjust return status - if necessary
990  """
991 
992  ProcUtils.remove(self.server_file)
993 
994  # if an anc type was turned off but its db_status bit was on, turn off the
995  # status bit so the user (and GUI) won't think anything's wrong
996  if not self.atteph:
997  if self.db_status & 4 and self.opt_flag & 1:
998  self.db_status = sub(self.db_status, 4)
999  if self.db_status & 8 and self.opt_flag & 2:
1000  self.db_status = sub(self.db_status, 8)
1001  if self.db_status & 16 and self.opt_flag & 4:
1002  self.db_status = sub(self.db_status, 16)
def set_opt_flag(self, key, off=False)
Definition: anc_utils.py:383
def get_start_end_info_from_xml(self, raw_xml)
Definition: anc_utils.py:160
def get_goci_time(goci_time_str)
Definition: anc_utils.py:143
def modis_timestamp(arg)
Definition: modis_utils.py:152
#define isdigit(c)
list(APPEND LIBS ${NETCDF_LIBRARIES}) find_package(GSL REQUIRED) include_directories($
Definition: CMakeLists.txt:8
def yearday(self, ancfile)
Definition: anc_utils.py:675
void print(std::ostream &stream, const char *format)
Definition: PrintDebug.hpp:38
def viirs_timestamp(arg)
Definition: viirs_utils.py:4
Definition: aerosol.c:136
def __init__(self, filename=None, start=None, stop=None, ancdir=None, ancdb='ancillary_data.db', anc_file=None, curdir=False, atteph=False, sensor=None, opt_flag=None, verbose=0, printlist=True, download=True, timeout=60, refreshDB=False, use_filename=False)
Definition: anc_utils.py:45
def locate(self, forcedl=False)
Definition: anc_utils.py:774