8 from datetime
import datetime
9 from datetime
import timedelta
11 import xml.etree.ElementTree
as ElementTree
12 from operator
import sub
13 from collections
import OrderedDict
29 from viirs.viirs_utils
import viirs_timestamp
37 DEFAULT_ANC_DIR_TEXT =
"$OCVARROOT"
42 utilities for ancillary file search
49 ancdb='ancillary_data.db',
85 self.
query_site =
"oceandata.sci.gsfc.nasa.gov"
86 self.
data_site =
"oceandata.sci.gsfc.nasa.gov"
90 Check validity of inputs to
94 print(
"ERROR: No L1A_or_L1B_file or start time specified!")
99 print(
"ERROR: No FILE or MISSION specified.")
102 and self.
sensor.lower() !=
"aqua" and self.
sensor.lower() !=
"terra":
103 print(
"ERROR: Mission must be 'aqua', 'modisa', 'terra', or 'modist' ")
107 print(
"ERROR: The '--use-current' and '--ancdir' arguments cannot be used together.")
108 print(
" Please use only one of these options.")
111 if self.
start is not None:
113 print(
"ERROR: Start time must be in YYYYDDDHHMMSS or YYYY-MM-DDTHH:MM:SS format and YYYY is between 1978 and 2030.")
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.")
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.
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
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'
156 data_elem = elem.find(
'Data')
157 value_elem = data_elem.find(
'DataFromFile')
158 return value_elem.text.strip()
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
167 xml_root = ElementTree.fromstring(raw_xml)
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.")
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.")
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.")
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.")
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')
221 self.
server_file =
"{0:>s}.anc.server".format(
'.'.join(self.
base.split(
'.')[0:-1]))
223 self.
anc_file =
"{0:>s}.atteph".format(
'.'.join(self.
base.split(
'.')[0:-1]))
225 self.
anc_file =
"{0:>s}.anc".format(
'.'.join(self.
base.split(
'.')[0:-1]))
241 self.
server_file =
"{0:>s}.anc.server".format(
'.'.join(self.
base.split(
'.')[0:-1]))
257 if self.
start is None:
261 if not os.path.exists(self.
filename):
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.")
266 print(
"ERROR: Filename must be in XYYYYDDDHHMMSS format where X is the")
267 print(
"sensor letter and YYYY is between 1978 and 2030.")
272 print(
"*** ERROR: Input file doesn't exist and mission not set...bailing out...")
279 print(
"Determining pass start and end times...")
280 senchk = ProcUtils.check_sensor(self.
filename)
282 if re.search(
'(Aqua|Terra)', senchk):
285 elif senchk.find(
"viirs") == 0:
287 elif senchk.find(
"aquarius") == 0:
289 elif re.search(
'goci', senchk):
291 elif re.search(
'hawkeye', senchk):
293 elif senchk.find(
"hico") == 0:
295 elif re.search(
'meris', senchk):
297 elif re.search(
'(S2A|S2B)', senchk):
299 elif re.search(
'ocm2', senchk):
301 elif re.search(
'ETM', senchk):
303 elif re.search(
'(3A|3B)', senchk):
305 elif re.search(
'sgli', senchk):
307 elif re.search(
'tm', senchk):
309 elif re.search(
'L9', senchk):
314 mime_data = MetaUtils.get_mime_data(self.
filename)
315 if MetaUtils.is_netcdf4(mime_data):
316 metadata = MetaUtils.dump_metadata(self.
filename)
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]
326 self.
start = starttime[0:19]
328 if stoptime.find(
'T') != -1:
329 self.
stop = stoptime[0:19]
332 self.
stop = stoptime[0:19]
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()
340 if not starttime
or not startdate
or not stoptime
or not stopdate:
341 err_msg =
'ERROR: For ' + self.
base +
' could not determine: '
343 err_msg = err_msg +
' start time'
346 err_msg = err_msg +
', start date'
348 err_msg = err_msg +
' start date'
350 if not starttime
or not startdate:
351 err_msg = err_msg +
', stoptime'
353 err_msg = err_msg +
' stop time'
355 if not starttime
or not startdate
or not stoptime:
356 err_msg = err_msg +
', stop date'
358 err_msg = err_msg +
' stop date'
359 err_msg = err_msg +
'. Exiting.'
362 print(
"l1info reported the following error:")
363 print(
' {0}'.format(info[1]))
368 self.
start = startdate +
'T' + starttime[0:8]
369 self.
stop = stopdate +
'T' + stoptime[0:8]
379 print(
"Start time: " +
str(self.
start))
380 print(
"End time : " +
str(self.
stop))
385 set up the opt_flag for display_ancillary_data (type=anc)
387 0 - just the basics, MET/OZONE
392 optkey = {
'sst': 1,
'no2': 2,
'ice': 4}
401 Checks local db for anc files.
404 if len(os.path.dirname(self.
ancdb)):
405 self.
dirs[
'log'] = os.path.dirname(self.
ancdb)
408 if not os.path.exists(self.
dirs[
'log']):
410 print(
'''Directory %s does not exist.
411 Using current working directory for storing the ancillary database file: %s''' % (self.
dirs[
'log'], self.
ancdb))
416 if not os.path.exists(self.
ancdb):
419 ancdatabase = db.ancDB(dbfile=self.
ancdb)
420 if not os.path.getsize(self.
ancdb):
422 print(
"Creating database: %s " % self.
ancdb)
424 ancdatabase.create_db()
428 print(
"Searching database: %s " % self.
ancdb)
431 filekey = os.path.basename(self.
filename)
435 start_obj = datetime.strptime(self.
start,
'%Y%j%H%M%S')
436 self.
start = datetime.strftime(start_obj,
'%Y-%m-%dT%H:%M:%S')
437 if self.
stop is not None:
438 stop_obj = datetime.strptime(self.
stop,
'%Y%j%H%M%S')
439 self.
stop = datetime.strftime(stop_obj,
'%Y-%m-%dT%H:%M:%S')
440 status = ancdatabase.check_file(filekey,starttime=self.
start)
443 self.
files = ancdatabase.get_ancfiles(filekey, self.
atteph, starttime=self.
start)
446 self.
files[anckey] = os.path.basename(self.
files[anckey])
448 self.
start, self.
stop = ancdatabase.get_filetime(filekey, starttime=self.
start)
450 ancdatabase.delete_record(filekey, starttime=self.
start)
452 ancdatabase.closeDB()
457 print(
"Warning! Non-optimal data exist in local repository.")
458 print(
"Consider re-running with the --refreshDB option to check for optimal ancillary files")
467 Execute the display_ancillary_files search and populate the locate cache database
472 with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'missionID.json'),
'r')
as msn_file:
473 msn = json.load(msn_file)
483 anctype =
'anc_data_api'
488 if self.
sensor ==
'aquarius':
499 start_obj = datetime.strptime(self.
start,
'%Y-%m-%dT%H:%M:%S')
500 time_change = timedelta(minutes=5)
501 stop_obj = start_obj + time_change
502 self.
stop = datetime.strftime(stop_obj,
'%Y-%m-%dT%H:%M:%S')
503 anc_str =
'?&m=' + msnchar +
'&s=' + self.
start +
'&e=' + self.
stop +
'&missing_tags=1'
504 dlstat = ProcUtils.httpdl(self.
query_site,
'/'.join([
'/api', anctype, anc_str]),
505 os.path.abspath(os.path.dirname(self.
server_file)),
511 anc_str =
'?filename=' + os.path.basename(self.
filename) +
'&missing_tags=1'
513 '/'.join([
'/api', anctype, anc_str]),
514 os.path.abspath(os.path.dirname(self.
server_file)),
520 anc_str =
'?&m=' + msnchar +
'&s=' + self.
start +
'&e=' + self.
stop +
'&missing_tags=1'
522 '/'.join([
'/api', anctype, anc_str]),
523 os.path.abspath(os.path.dirname(self.
server_file)),
531 print(
"Error retrieving ancillary file list")
535 results = json.load(data_file)
540 print(
"The time duration provided is longer than 2 hours. please try with a shorter duration." )
541 print(
"No parameter file created.")
547 print(
"Stop time is earlier than start time." )
548 print(
"No parameter file created.")
554 for f
in results[
'files']:
555 if (len(
str(f[1]))) == 0:
557 if re.search(
'MET', f[0]):
559 if re.search(
'OZONE', f[0]):
561 if re.search(
'sst', f[0]):
563 if re.search(
'no2', f[0]):
565 if re.search(
'ice', f[0]):
568 if re.search(
'ATT', f[0]):
570 if re.search(
'EPH', f[0]):
574 if re.search(
'ATT|EPH', f[0]):
577 if re.search(
'ATT', f[0]):
579 if re.search(
'EPH', f[0]):
582 if re.search(
'MET|OZONE|sst|ice|AER|GEO', f[0]):
612 print(
"ERROR: The display_ancillary_files.cgi script encountered an error and returned the following text:")
619 print(
"No ancillary files currently exist that correspond to the start time " + self.
start)
620 print(
"No parameter file created (l2gen defaults to the climatologies).")
626 print(
"No att/eph files currently exist that correspond to the start time " + self.
start)
632 if not len(self.
files[f]):
633 print(
"ERROR: display_ancillary_files.cgi script returned blank entry for %s. Exiting." % f)
636 ancdatabase = db.ancDB(dbfile=self.
ancdb)
638 if not os.path.exists(ancdatabase.dbfile)
or os.path.getsize(ancdatabase.dbfile) == 0:
640 ancdatabase.create_db()
646 for anctype
in self.
files:
647 if self.
files[anctype] ==
'missing':
648 missing.append(anctype)
651 path = self.
dirs[
'anc']
654 path = os.path.join(path, year, day)
657 filekey = os.path.basename(self.
filename)
660 ancdatabase.insert_record(satfile=filekey, starttime=self.
start, stoptime=self.
stop, anctype=anctype,
664 ancdatabase.closeDB()
666 for anctype
in missing:
667 self.
files.__delitem__(anctype)
670 if ancfile.startswith(
'RIM_'):
671 ymd = ancfile.split(
'_')[2]
672 dt = datetime.strptime(ymd,
'%Y%m%d')
673 year = dt.strftime(
'%Y')
674 day = dt.strftime(
'%j')
677 if ancfile.startswith(
'MERRA'):
678 ymd = ancfile.split(
'.')[4]
679 dt = datetime.strptime(ymd,
'%Y%m%d')
680 year = dt.strftime(
'%Y')
681 day = dt.strftime(
'%j')
685 if re.search(
'[\d]{8}T', ancfile)
or re.search(
'[\d]{14}', ancfile):
686 ymd = re.search(
'[\d]{8}', ancfile).
group()
687 dt = datetime.strptime(ymd,
'%Y%m%d')
688 year = dt.strftime(
'%Y')
689 day = dt.strftime(
'%j')
692 elif re.search(
'[\d]{13}', ancfile):
693 ymd = re.search(
'[\d]{7}', ancfile).
group()
698 elif re.search(
'[\d]{12}', ancfile):
699 ymd = re.search(
'[\d]{8}', ancfile).
group()
700 dt = datetime.strptime(ymd,
'%Y%m%d')
701 year = dt.strftime(
'%Y')
702 day = dt.strftime(
'%j')
705 elif re.search(
'[\d]{11}', ancfile):
706 ymd = re.search(
'[\d]{7}', ancfile).
group()
711 elif re.search(
'[\d]{10}', ancfile):
712 ymd = re.search(
'[\d]{8}', ancfile).
group()
713 dt = datetime.strptime(ymd,
'%Y%m%d')
714 year = dt.strftime(
'%Y')
715 day = dt.strftime(
'%j')
718 elif re.search(
'[\d]{9}', ancfile):
719 ymd = re.search(
'[\d]{7}', ancfile).
group()
724 elif re.search(
'[\d]{8}', ancfile):
725 ymd = re.search(
'[\d]{8}', ancfile).
group()
726 dt = datetime.strptime(ymd,
'%Y%m%d')
727 year = dt.strftime(
'%Y')
728 day = dt.strftime(
'%j')
731 elif re.search(
'[\d]{7}', ancfile):
732 ymd = re.search(
'[\d]{7}', ancfile).
group()
737 if ancfile.startswith(
'GMAO'):
738 ymd = ancfile.split(
'.')[1][0:8]
739 dt = datetime.strptime(ymd,
'%Y%m%d')
740 year = dt.strftime(
'%Y')
741 day = dt.strftime(
'%j')
746 dt = datetime.strptime(ymd,
'%Y%m%d')
747 year = dt.strftime(
'%Y')
748 day = dt.strftime(
'%j')
751 if ancfile.startswith(
'SIF'):
752 yyyyddd = ancfile.split(
'.')[0]
754 elif ancfile.startswith(
'PERT_'):
755 yyyyddd = ancfile.split(
'_')[2]
757 elif self.
atteph and not re.search(
".(att|eph)$", ancfile):
758 yyyyddd = ancfile.split(
'.')[1]
764 year = yyyyddd[offset:offset + 4]
765 day = yyyyddd[offset + 4:offset + 7]
770 Find the files on the local system or download from OBPG
776 if re.search(
'scat|atm|met|ozone|file|geo|aer', f):
779 if re.search(
'att|eph', f):
781 FILES.append(os.path.basename(self.
files[f]))
785 for FILE
in list(OrderedDict.fromkeys(FILES)):
791 self.
dirs[
'path'] =
'.'
792 if os.path.exists(FILE)
and forcedl
is False:
795 print(
" Found: %s" % FILE)
799 ancdir = self.
dirs[
'anc']
801 self.
dirs[
'path'] = os.path.join(ancdir, year, day)
802 if os.path.exists(os.path.join(ancdir, year, day, FILE))
and forcedl
is False:
805 print(
" Found: %s/%s" % (self.
dirs[
'path'], FILE))
815 print(
"Downloads disabled. The following missing file(s) will not be downloaded:")
822 print(
"Downloading '" + FILE +
"' to " + self.
dirs[
'path'])
823 status = ProcUtils.httpdl(self.
data_site,
''.join([
'/ob/getfile/', FILE]),
824 self.
dirs[
'path'], timeout=self.
timeout, uncompress=
True,force_download=forcedl,
830 print(
"%s is not newer than local copy, skipping download" % FILE)
832 print(
"*** ERROR: Authentication Failure retrieving:")
833 print(
"*** " +
'/'.join([self.
data_site,
'ob/getfile', FILE]))
834 print(
"*** Please check that your ~/.netrc file is setup correctly and has proper permissions.")
836 print(
"*** see: https://oceancolor.gsfc.nasa.gov/data/download_methods/")
839 print(
"*** ERROR: The HTTP transfer failed with status code " +
str(status) +
".")
840 print(
"*** Please check your network connection and for the existence of the remote file:")
841 print(
"*** " +
'/'.join([self.
data_site,
'ob/getfile', FILE]))
843 print(
"*** Also check to make sure you have write permissions under the directory:")
844 print(
"*** " + self.
dirs[
'path'])
848 ProcUtils.remove(os.path.join(self.
dirs[
'path'], FILE))
854 if re.search(
'met|ozone|file|aer|geo', f):
857 if re.search(
'att|eph', f):
859 if FILE == self.
files[f]:
860 self.
files[f] = os.path.join(self.
dirs[
'path'], FILE)
864 create the .anc parameter file
871 if self.
sensor ==
'aquarius':
872 inputs = {
'MET': {
'bitval': 1,
'required': [
'met1',
'met2',
'atm1',
'atm2']},
873 'SST': {
'bitval': 4,
'required': [
'sstfile1',
'sstfile2']},
874 'SeaIce': {
'bitval': 16,
'required': [
'icefile1',
'icefile2']},
875 'Salinity': {
'bitval': 32,
'required': [
'sssfile1',
'sssfile2']},
876 'XRAY': {
'bitval': 64,
'required': [
'xrayfile1']},
878 'TEC': {
'bitval': 256,
'required': [
'tecfile']},
879 'SWH': {
'bitval': 512,
'required': [
'swhfile1',
'swhfile2']},
880 'Frozen': {
'bitval': 1024,
'required': [
'frozenfile1',
'frozenfile2']},
881 'GEOS': {
'bitval': 2048,
'required': [
'geosfile']},
882 'ARGOS': {
'bitval': 4096,
'required': [
'argosfile1',
'argosfile2']},
883 'SIF': {
'bitval': 8192,
'required': [
'sif']},
884 'PERT': {
'bitval': 16384,
'required': [
'pert']},
885 'Matchup': {
'bitval': 32768,
'required': [
'sssmatchup']},
886 'Rainfall': {
'bitval': 65536,
'required': [
'rim_file']}}
888 if self.
db_status & inputs[anc][
'bitval']:
889 NONOPT =
" ".join([NONOPT, anc])
891 for ancfile
in (inputs[anc][
'required']):
892 if ancfile
not in self.
files:
893 NONOPT =
" ".join([NONOPT, anc])
894 print(
'*** WARNING: No optimal {0} files found.'.format(ancfile))
900 NONOPT =
" ".join([NONOPT,
'MET'])
902 for key
in ([
'met1',
'met2',
'met3']):
903 if key
not in self.
files:
904 NONOPT =
" ".join([NONOPT,
'MET'])
905 print(
"*** WARNING: No optimal MET files found.")
909 NONOPT =
" ".join([NONOPT,
'OZONE'])
911 for key
in ([
'ozone1',
'ozone2',
'ozone3']):
912 if key
not in self.
files:
913 NONOPT =
" ".join([NONOPT,
'OZONE'])
914 print(
"*** WARNING: No optimal OZONE files found.")
918 NONOPT =
" ".join([NONOPT,
'SST'])
919 print(
"*** WARNING: No optimal SST files found.")
922 NONOPT =
" ".join([NONOPT,
'NO2'])
923 print(
"*** WARNING: No optimal NO2 files found.")
926 NONOPT =
" ".join([NONOPT,
'Sea Ice'])
927 print(
"*** WARNING: No optimal ICE files found.")
931 for key
in sorted(self.
files.keys()):
933 if key.find(
'att') != -1
or key.find(
'eph') != -1:
934 ancpar.write(
'='.join([key, self.
files[key]]) +
'\n')
936 if key.find(
'att') == -1
and key.find(
'eph') == -1:
937 ancpar.write(
'='.join([key, self.
files[key]]) +
'\n')
944 print(
"All required attitude and ephemeris files successfully determined and downloaded.")
946 print(
"All required attitude and ephemeris files successfully determined.")
949 print(
"Created '" + self.
anc_file +
"' l2gen parameter text file:\n")
956 print(
"No optimal ancillary files were found.")
957 print(
"No parameter file was created (l2gen defaults to the climatological ancillary data).")
962 print(
"*** WARNING: The following ancillary data types were missing or are not optimal: " + NONOPT)
964 print(
"*** Beware that certain MET and OZONE files just chosen by this program are not optimal.")
965 print(
"*** For near real-time processing the remaining files may become available soon.")
967 print(
"*** Beware that certain MET files just chosen by this program are not optimal.")
968 print(
"*** For near real-time processing the remaining files may become available soon.")
970 print(
"*** Beware that certain OZONE files just chosen by this program are not optimal.")
971 print(
"*** For near real-time processing the remaining files may become available soon.")
976 print(
"- All optimal ancillary data files were determined and downloaded. -")
979 print(
"- All optimal ancillary data files were determined. -")
983 remove the temporary 'server' file and adjust return status - if necessary