3 from datetime
import datetime
5 from math
import ceil, floor
8 from shlex
import quote
20 if "km" in resolution:
21 resvalue =
float(resolution.strip(
'km')) * 1000.
22 elif "m" in resolution:
23 resvalue =
float(resolution.strip(
'm'))
24 elif "deg" in resolution:
25 resvalue =
float(resolution.strip(
'deg')) * 111312.
29 elif resvalue < 250.0:
31 elif resvalue < 500.0:
33 elif resvalue < 1150.0:
35 elif resvalue < 2300.0:
37 elif resvalue < 4600.0:
39 elif resvalue < 9200. :
46 cmd = [
'ncdump',
'-h', filepath]
47 result = subprocess.run(cmd, shell=
False, capture_output=
True, text=
True)
49 if not result.returncode:
50 for line
in result.stdout.splitlines():
51 if 'processing_level' in line:
55 if re.search(
r'"L2',levelstr):
57 elif re.search(
r'"L3',levelstr):
63 logging.error(errormsg)
68 Execute a process on the system
70 logging.info(
"Running: " +
' '.join(command))
72 result = subprocess.run(command, shell=
False, capture_output=
True, text=
True)
73 std_out = result.stdout
74 err_out = result.stderr
77 logging.debug(std_out)
79 logging.error(err_out)
80 whoops(
"Exiting: {command} returned a status {status}".
format(command=
' '.join(command),status=result.returncode), result.returncode)
83 logging.debug(std_out)
85 logging.debug(err_out)
89 Set up and run a program that accepts keyword=value command line arguemnts ala CLO
91 logging.info(
'\n###### {program} ######\n'.
format(program=program))
94 params = [
"%s=%s" % (k,v)
for k, v
in inputs.items()]
99 if __name__ ==
"__main__":
101 parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,description=
'''\
102 generate mapped output from a SeaDAS supported satellite data files
103 arguments can be specified on the commandline or in a parameter file
104 the two methods can be used together, with commandline over-riding the parfile''',add_help=
True)
105 parser.add_argument(
'--parfile',
'-p', type=str, help=
'input parameter file')
106 parser.add_argument(
'--ifile',
'-i', type=str, help=
'input file or text file list of files')
107 parser.add_argument(
'--geofile',
'-g', type=str,help=
'geolocation file or text file list of files')
108 parser.add_argument(
'--ofile',
'-o', type=str, help=
'output file name; default: <ifile>.MAP.<oformat ext>')
109 parser.add_argument(
'--logfile',
'-l', type=str, default=
'mapgen_<timestamp>.log', help=
'''\
111 default: mapgen_<timestamp>.log
112 <timestamp> is in seconds since Jan 1, 1970 00:00:00
113 this file is deleted if verbose is not set and no errors
114 occur during processing''')
116 parser.add_argument(
'--use_rgb', action=
'store_true', default=
False, help=
'''\
117 generate an RGB image output
118 default: a pseudo-true color image with bands to use
119 controlled by --product_rgb option''')
120 prodGroup = parser.add_mutually_exclusive_group()
121 prodGroup.add_argument(
'--product', type=str, help=
'product(s) to map; comma separated')
122 prodGroup.add_argument(
'--product_rgb', type=str,help=
'''\
123 comma separated string of RGB products
124 e.g., product_rgb=rhos_645,rhos_555,rhos_469
125 default: sensor specific, see
126 $OCDATAROOT/<sensor>/l1mapgen_defaults.par''')
128 parser.add_argument(
'--resolution',
'-r', type=str, default=
'2.0km', help=
'''\
129 #.#: width of a pixel in meters
130 #.#km: width of a pixel in kilometers
131 #.#deg: width of a pixel in degrees''')
132 parser.add_argument(
'--oformat', choices=[
'netcdf4',
'png',
'ppm',
'tiff'], type=str, default=
"png", help=
'''\
133 netcdf4: Network Common Data Form v4 file
134 can contain more than one product
135 png: Portable Network Graphics format image
136 ppm: Portable PixMap format image
137 tiff: Tagged Image File Format with georeference tags''')
138 parser.add_argument(
'--use_transparency',
'-t',action=
'store_true', default=
False, help=
'make missing data transparent\nonly valid for color PNG and TIFF output')
139 parser.add_argument(
'--north',
'-n', type=float, help=(
'northern-most latitude; default: input file max lßatitude'))
140 parser.add_argument(
'--south',
'-s', type=float, help=(
'southern-most latitude; default: input file min latitude'))
141 parser.add_argument(
'--east',
'-e', type=float, help=(
'eastern-most latitude; default: input file max longitude'))
142 parser.add_argument(
'--west',
'-w', type=float, help=(
'western-most latitude; default: input file min longitude'))
143 parser.add_argument(
'--projection', type=str,default=
'platecarree', help=
'''\
144 "proj" projection string or one of the following:
145 platecarree: Plate Carree (cylindrical) projection
146 projection="+proj=eqc +lat_0=<central_meridian>"
147 mollweide: Mollweide projection
148 projection="+proj=moll +lat_0=<central_meridian>"
149 lambert: Lambert conformal conic projection
150 projection="+proj=lcc +lat_0=<central_meridian>"
151 albersconic: Albers equal-area conic projection
152 projection="+proj=aea +lat_0=<central_meridian>"
153 mercator: Mercator cylindrical map projection
154 projection="+proj=merc +lat_0=<central_meridian>"
155 ease2: Ease Grid 2 projection
156 projection="+proj=cea +lon_0=0 +lat_ts=30 +ellps=WGS84
157 +datum=WGS84 +units=m +lat_0=<central_meridian>"''')
158 parser.add_argument(
'--central_meridian', type=float, help=
'central meridian to use for projection in degrees east')
159 parser.add_argument(
'--palfile', type=str, help=
'palette filename\ndefault: see $OCDATAROOT/common/product.xml')
160 parser.add_argument(
'--fudge', type=float,default=1.0, help=
'factor used to modify pixel search radius for mapping')
161 parser.add_argument(
'--datamin', type=float, help=
'minimum value for scaling (default from product.xml)')
162 parser.add_argument(
'--datamax', type=float, help=
'maximum value for scaling (default from product.xml)')
163 parser.add_argument(
'--scale_type', choices=[
'linear',
'log',
'arctan'],type=str, help=
'data scaling method (default from product.xml)')
164 parser.add_argument(
'--threshold', type=float, help=
'minimum percentage of filled pixels for image generation\ndefault: 0')
165 parser.add_argument(
'--trimNSEW', action=
'store_false',default=
True, help=
'do not trim output to match input NSEW range')
166 parser.add_argument(
'--write_projtext', action=
'store_true',default=
False, help=
'write projection information to a text file (for mapgen_overlay script)')
167 parser.add_argument(
'--keep-intermediates', action=
'store_true',default=
False, help=
'do not delete the intermediate L2/L3B files produced')
168 parser.add_argument(
'--verbose',
'-v',action=
'count',default=0, help=
"let's get chatty; each occurrence increases verbosity\ndefault: error\n-v info -vv debug'")
170 args=parser.parse_args()
172 sanitize = [
'ifile',
'ofile',
'logfile',
'geofile',
'parfile',
'product',
'resolution']
173 for arg
in vars(args):
175 value = getattr(args,arg)
177 setattr(args,arg,quote(value))
179 if 'timestamp' in args.logfile:
180 setattr(args,
'logfile',
''.join([
'mapgen_',datetime.now().strftime(
'%s'),
'.log']))
182 levels = [logging.ERROR, logging.INFO, logging.DEBUG]
184 verbosity = max(0, min(args.verbose, len(levels)-1))
186 logging.basicConfig(filename=args.logfile,
187 format=
'-%(levelname)s- %(asctime)s - %(message)s',
188 level=levels[verbosity])
189 logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
191 if not os.getenv(
"OCSSWROOT"):
192 whoops(
"You must define the OCSSW enviroment for me to help you...")
196 param_proc.parseParFile()
197 for param
in (param_proc.params[
'main'].keys()):
198 value = param_proc.params[
'main'][param]
199 if param
in sanitize:
201 if hasattr(args,param):
202 setattr(args,param,value)
203 elif param ==
'file':
208 whoops(
"\nYou must specify an input file either on the command line or in a parameter file")
210 if not os.path.isfile(args.ifile):
211 whoops(
"\nYou must specify a valid input file either on the command line or in a parameter file")
214 extension = args.oformat
215 if extension ==
'netcdf4':
217 setattr(args,
'ofile',args.ifile +
'.MAP.' + extension)
219 geo_opts = [
"north",
"south",
"east",
"west"]
220 l2bin_opts = [
"resolution"]
221 l3map_opts = [
"product",
"use_rgb",
"product_rgb",
"oformat",
"resolution",
222 "projection",
"central_meridian",
"palfile",
"fudge",
"threshold",
223 "datamin",
"datamax",
"scale_type",
"palfile",
"use_transparency",
224 "trimNSEW",
"write_projtext"]
225 tmpfile_l2gen =
"mapgen_l2gen_list.txt"
226 tmpfile_l2bin =
"mapgen_tmp.l2bin.nc"
229 msg =
"Parameters passed to or assumed by mapgen:\n"
230 for key,value
in (vars(args)).items():
231 msg +=
"\t{k}={v}\n".
format(k=key,v=value)
237 if 'text' in MetaUtils.get_mime_data(args.ifile)
and (
not re.search(
'MTL.txt', args.ifile))
and (
not re.search(
'xfdumanifest.xml', args.ifile)):
238 with open(args.ifile,
'r')
as lstfile:
239 ifile_list = lstfile.readlines()
240 for ifile
in ifile_list:
241 if not os.path.isfile(ifile.strip()):
242 whoops(
"Hmmm...seems I don't recognize this input: {ifile}".
format(ifile=ifile))
244 ifiles.append(ifile.strip())
247 whoops(
"Seems you are mixing input types. While I might be able to make sense of your request, it'd be nicer to pass me just on type of file at a time...")
251 ifiles.append(args.ifile)
254 if not args.use_rgb
and not args.product:
255 whoops(
"My cousin has the artificial intelligence in the family...I'll need you to tell me what product(s) you wish to map...")
256 if ftype == 3
and len(ifiles) > 1:
257 whoops(
"I can only map one Level 3 file at a time...")
264 whoops(
"You gave me Level 1 data, but didn't tell me you wanted RGB output, and I don't want to assume... If you want another product, I'll need Level 2 inputs")
266 if args.oformat ==
'netcdf4':
267 whoops(
"netCDF output is not supported for RGB images")
271 if 'text' in MetaUtils.get_mime_data(args.geofile):
272 with open(args.geofile,
'r')
as geolistfile:
273 geofiles = geolistfile.readlines()
275 geofiles.append(args.geofile)
276 if len(geofiles) != len(ifiles):
277 whoops(
"Number of provided geofiles does not match number of provided ifiles...")
279 l2filelst = open(tmpfile_l2gen,
'w')
280 for i, l1file
in enumerate(ifiles):
282 logging.info(
"{cnt}: {ifile}".
format(cnt=i, ifile=l1file))
285 l1file = l1file.strip()
289 clo[
'geofile'] = geofiles[i].strip()
291 ofile =
"{filebase}_{cnt:02}.nc".
format(filebase=tmpfile_l2gen.replace(
'_list.txt',
''),cnt=i+1)
294 clo[
'l2prod'] = args.product_rgb
298 sensor = file_typer.get_sensor()
301 sensor_dir = dirs_by_sensor[
'dir']
302 if 'subdir' in dirs_by_sensor:
303 sensor_sub_dir = dirs_by_sensor[
'subdir']
305 filename_l3mapgen_defaults = os.path.join(os.getenv(
'OCDATAROOT'),
306 sensor_dir, sensor_sub_dir,
'l3mapgen_defaults.par')
307 if not os.path.exists(filename_l3mapgen_defaults):
308 filename_l3mapgen_defaults = os.path.join(os.getenv(
'OCDATAROOT'),
309 sensor_dir,
'l3mapgen_defaults.par')
311 filename_l3mapgen_defaults = os.path.join(os.getenv(
'OCDATAROOT'),
312 sensor_dir,
'l3mapgen_defaults.par')
314 param_proc.parseParFile()
315 for param
in (param_proc.params[
'main'].keys()):
316 if param ==
'product_rgb':
317 clo[
'l2prod'] = args.product_rgb = param_proc.params[
'main'][
'product_rgb']
319 clo[
'proc_sst'] =
'0'
321 for geo_opt
in geo_opts:
322 if getattr(args,geo_opt)
is not None:
323 clo[geo_opt] =
str(getattr(args,geo_opt))
326 msg =
"l2gen parameters for {ofile}\n".
format(ofile=ofile)
327 for key,value
in (vars(args)).items():
328 msg +=
"\t{k}={v}\n".
format(k=key,v=value)
332 logging.info(
"Generated L2 file for input: {ifile}".
format(ifile=l1file))
334 l2filelst.write(ofile+
'\n')
342 clo[
'flaguse'] =
'ATMFAIL,BOWTIEDEL'
344 if args.product
and re.search(
r'sst.?',args.product):
346 clo[
'flaguse'] =
'LAND,BOWTIEDEL'
348 clo[
'flaguse'] =
'ATMFAIL,CLDICE,BOWTIEDEL'
349 clo[
'l3bprod'] = args.product
351 clo[
'ifile'] = tmpfile_l2gen
353 clo[
'ifile'] = args.ifile
355 clo[
'ofile'] = tmpfile_l2bin
356 clo[
'area_weighting'] =
'1'
357 clo[
'prodtype'] =
"regional"
359 if args.north
is not None:
360 clo[
'latnorth'] = ceil(
float(args.north))
361 if args.south
is not None:
362 clo[
'latsouth'] = floor(
float(args.south))
363 if args.west
is not None:
364 clo[
'lonwest'] = floor(
float(args.west))
365 if args.east
is not None:
366 clo[
'loneast'] = ceil(
float(args.east))
367 for l2bin_opt
in l2bin_opts:
368 if getattr(args,l2bin_opt)
is not None:
369 if l2bin_opt ==
'resolution':
370 clo[l2bin_opt] =
getBinRes(
str(getattr(args,l2bin_opt)))
372 clo[l2bin_opt] =
str(getattr(args,l2bin_opt))
375 msg =
"l2bin parameters:\n"
376 for key,value
in (vars(args)).items():
377 msg +=
"\t{k}={v}\n".
format(k=key,v=value)
381 logging.info(
"Generated bin file {binfile}".
format(binfile=tmpfile_l2bin))
388 clo[
'ifile'] = tmpfile_l2bin
390 clo[
'ifile'] = args.ifile
392 clo[
'interp'] =
'nearest'
394 for opt
in l3map_opts + geo_opts:
395 if getattr(args,opt)
is not None:
397 if type(getattr(args,opt))
is bool:
398 if getattr(args,opt)
is True:
401 clo[opt] =
str(getattr(args,opt))
404 clo[
'product'] = args.product
406 if args.oformat ==
'netcdf4':
407 clo[
'ofile'] = args.ofile
410 if len(args.product.split(
',')) > 1:
411 ofile = args.ofile.replace(args.oformat,
'') +
'PRODUCT' +
'.' + args.oformat
414 clo[
'product'] = args.product
415 clo[
'ofile'] = args.ofile
417 clo[
'ofile'] = args.ofile
421 msg =
"l3mapgen parameters:\n"
422 for key,value
in (vars(args)).items():
423 msg +=
"\t{k}={v}\n".
format(k=key,v=value)
427 logging.info(
"Generated map file(s)")
430 if not args.keep_intermediates:
431 if os.path.exists(tmpfile_l2gen):
432 l2filelst = open(tmpfile_l2gen,
'r')
433 for l2f
in l2filelst.readlines():
435 if os.path.exists(l2f):
437 logging.info(
"removing: {rmfile}".
format(rmfile=l2f))
440 os.remove(tmpfile_l2gen)
441 if os.path.exists(tmpfile_l2bin):
443 logging.info(
"removing: {rmfile}".
format(rmfile=tmpfile_l2bin))
444 os.remove(tmpfile_l2bin)
447 if os.stat(args.logfile).st_size == 0:
448 os.remove(args.logfile)