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|JPSS-1|JPSS-2|Suomi_NPP") == 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]
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):
424 ancdatabase.create_db()
431 filekey = os.path.basename(self.
filename)
432 if (re.search(
'manifest.safe', self.
filename)
or re.search(
'xfdumanifest.xml', self.
filename)):
434 filekey = os.path.join(os.path.basename(os.path.dirname(self.
filename)), filekey)
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)
446 self.
files = ancdatabase.get_ancfiles(filekey, self.
atteph, starttime=self.
start)
449 self.
files[anckey] = os.path.basename(self.
files[anckey])
451 self.
start, self.
stop = ancdatabase.get_filetime(filekey, starttime=self.
start)
453 ancdatabase.delete_record(filekey, starttime=self.
start)
455 ancdatabase.closeDB()
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")
470 Execute the display_ancillary_files search and populate the locate cache database
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)
486 anctype =
'anc_data_api'
491 if self.
sensor ==
'aquarius':
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)),
514 anc_str =
'?filename=' + os.path.basename(self.
filename) +
'&missing_tags=1'
516 '/'.join([
'/api', anctype, anc_str]),
517 os.path.abspath(os.path.dirname(self.
server_file)),
523 anc_str =
'?&m=' + msnchar +
'&s=' + self.
start +
'&e=' + self.
stop +
'&missing_tags=1'
525 '/'.join([
'/api', anctype, anc_str]),
526 os.path.abspath(os.path.dirname(self.
server_file)),
534 print(
"Error retrieving ancillary file list")
538 results = json.load(data_file)
543 print(
"The time duration provided is longer than 2 hours. please try with a shorter duration." )
544 print(
"No parameter file created.")
550 print(
"Stop time is earlier than start time." )
551 print(
"No parameter file created.")
557 for f
in results[
'files']:
558 if (len(
str(f[1]))) == 0:
560 if re.search(
'MET', f[0]):
562 if re.search(
'OZONE', f[0]):
564 if re.search(
'sst', f[0]):
566 if re.search(
'no2', f[0]):
568 if re.search(
'ice', f[0]):
571 if re.search(
'ATT', f[0]):
573 if re.search(
'EPH', f[0]):
577 if re.search(
'ATT|EPH', f[0]):
580 if re.search(
'ATT', f[0]):
582 if re.search(
'EPH', f[0]):
585 if re.search(
'MET|OZONE|sst|ice|AER|GEO', f[0]):
615 print(
"ERROR: The display_ancillary_files.cgi script encountered an error and returned the following text:")
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).")
629 print(
"No att/eph files currently exist that correspond to the start time " + self.
start)
635 if not len(self.
files[f]):
636 print(
"ERROR: display_ancillary_files.cgi script returned blank entry for %s. Exiting." % f)
639 ancdatabase = db.ancDB(dbfile=self.
ancdb)
641 if not os.path.exists(ancdatabase.dbfile)
or os.path.getsize(ancdatabase.dbfile) == 0:
643 ancdatabase.create_db()
649 for anctype
in self.
files:
650 if self.
files[anctype] ==
'missing':
651 missing.append(anctype)
654 path = self.
dirs[
'anc']
657 path = os.path.join(path, year, day)
660 filekey = os.path.basename(self.
filename)
661 if (re.search(
'manifest.safe', self.
filename)
or re.search(
'xfdumanifest.xml', self.
filename)):
663 filekey = os.path.join(os.path.basename(os.path.dirname(self.
filename)), filekey)
666 ancdatabase.insert_record(satfile=filekey, starttime=self.
start, stoptime=self.
stop, anctype=anctype,
670 ancdatabase.closeDB()
672 for anctype
in missing:
673 self.
files.__delitem__(anctype)
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')
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')
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')
698 elif re.search(
'[\d]{13}', ancfile):
699 ymd = re.search(
'[\d]{7}', ancfile).
group()
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')
711 elif re.search(
'[\d]{11}', ancfile):
712 ymd = re.search(
'[\d]{7}', ancfile).
group()
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')
724 elif re.search(
'[\d]{9}', ancfile):
725 ymd = re.search(
'[\d]{7}', ancfile).
group()
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')
737 elif re.search(
'[\d]{7}', ancfile):
738 ymd = re.search(
'[\d]{7}', ancfile).
group()
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')
752 dt = datetime.strptime(ymd,
'%Y%m%d')
753 year = dt.strftime(
'%Y')
754 day = dt.strftime(
'%j')
757 if ancfile.startswith(
'SIF'):
758 yyyyddd = ancfile.split(
'.')[0]
760 elif ancfile.startswith(
'PERT_'):
761 yyyyddd = ancfile.split(
'_')[2]
763 elif self.
atteph and not re.search(
".(att|eph)$", ancfile):
764 yyyyddd = ancfile.split(
'.')[1]
770 year = yyyyddd[offset:offset + 4]
771 day = yyyyddd[offset + 4:offset + 7]
776 Find the files on the local system or download from OBPG
782 if re.search(
'scat|atm|met|ozone|file|geo|aer', f):
785 if re.search(
'att|eph', f):
787 FILES.append(os.path.basename(self.
files[f]))
791 for FILE
in list(OrderedDict.fromkeys(FILES)):
797 self.
dirs[
'path'] =
'.'
798 if os.path.exists(FILE)
and forcedl
is False:
801 print(
" Found: %s" % FILE)
805 ancdir = self.
dirs[
'anc']
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:
811 print(
" Found: %s/%s" % (self.
dirs[
'path'], FILE))
821 print(
"Downloads disabled. The following missing file(s) will not be downloaded:")
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,
836 print(
"%s is not newer than local copy, skipping download" % FILE)
838 print(
"*** ERROR: Authentication Failure retrieving:")
840 print(
"*** Please check that your ~/.netrc file is setup correctly and has proper permissions.")
842 print(
"*** see: https://oceancolor.gsfc.nasa.gov/data/download_methods/")
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:")
849 print(
"*** Also check to make sure you have write permissions under the directory:")
854 ProcUtils.remove(os.path.join(self.
dirs[
'path'], FILE))
860 if re.search(
'met|ozone|file|aer|geo', f):
863 if re.search(
'att|eph', f):
865 if FILE == self.
files[f]:
866 self.
files[f] = os.path.join(self.
dirs[
'path'], FILE)
870 create the .anc parameter file
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']},
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']}}
894 if self.
db_status & inputs[anc][
'bitval']:
895 NONOPT =
" ".join([NONOPT, anc])
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))
906 NONOPT =
" ".join([NONOPT,
'MET'])
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.")
915 NONOPT =
" ".join([NONOPT,
'OZONE'])
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.")
924 NONOPT =
" ".join([NONOPT,
'SST'])
925 print(
"*** WARNING: No optimal SST files found.")
928 NONOPT =
" ".join([NONOPT,
'NO2'])
929 print(
"*** WARNING: No optimal NO2 files found.")
932 NONOPT =
" ".join([NONOPT,
'Sea Ice'])
933 print(
"*** WARNING: No optimal ICE files found.")
937 for key
in sorted(self.
files.keys()):
939 if key.find(
'att') != -1
or key.find(
'eph') != -1:
940 ancpar.write(
'='.join([key, self.
files[key]]) +
'\n')
942 if key.find(
'att') == -1
and key.find(
'eph') == -1:
943 ancpar.write(
'='.join([key, self.
files[key]]) +
'\n')
950 print(
"All required attitude and ephemeris files successfully determined and downloaded.")
952 print(
"All required attitude and ephemeris files successfully determined.")
955 print(
"Created '" + self.
anc_file +
"' l2gen parameter text file:\n")
962 print(
"No optimal ancillary files were found.")
963 print(
"No parameter file was created (l2gen defaults to the climatological ancillary data).")
968 print(
"*** WARNING: The following ancillary data types were missing or are not optimal: " + NONOPT)
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.")
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.")
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.")
982 print(
"- All optimal ancillary data files were determined and downloaded. -")
985 print(
"- All optimal ancillary data files were determined. -")
989 remove the temporary 'server' file and adjust return status - if necessary