11 from netCDF4
import Dataset
as NC
13 from pathlib
import Path
16 from netCDF4
import Dataset
as NC
17 from datetime
import datetime, timedelta, timezone
21 __version__ =
"1.0 2024-11-01"
48 "Scene Center Latitude",
49 "Scene Center Longitude",
50 "Scene Center Solar Zenith",
51 "Upper Left Latitude",
52 "Upper Left Longitude",
53 "Upper Right Latitude",
54 "Upper Right Longitude",
55 "Lower Left Latitude",
56 "Lower Left Longitude",
57 "Lower Right Latitude",
58 "Lower Right Longitude",
59 "Northernmost Latitude",
60 "Southernmost Latitude",
61 "Westernmost Longitude",
62 "Easternmost Longitude",
63 "Start Center Latitude",
64 "Start Center Longitude",
65 "End Center Latitude",
66 "End Center Longitude",
67 "Pixels per Scan Line",
68 "Number of Scan Lines",
69 "Scene Center Scan Line",
85 SDC.FLOAT32: np.float32,
86 SDC.FLOAT64: np.float64,
88 SDC.UINT16: np.uint16,
89 SDC.UINT32: np.uint32,
101 return hdfname.lower().replace(
" ",
"_")
114 print(
"Copying global attributes")
115 globalAttr = hdfFile.attributes()
117 for name, val
in globalAttr.items():
121 if (name
in SKIP_ATTRIBUTES):
continue
127 if (isinstance(val, str)):
131 val = np.float32(val)
if isinstance(val, float)
else np.int32(val)
139 errorAt =
"node_crossing_time"
140 node_crossing_str = globalAttr.get(
"Node Crossing Time")
141 ncd = node_crossing_str[:8]
142 nct = node_crossing_str[9:17]
143 fsec = node_crossing_str[18:21]
144 node_crossing = datetime.strptime(ncd+nct,
"%Y%m%d%H:%M:%S")
145 ncFile.setncattr(
"node_crossing_time",
str(node_crossing.strftime(
'%Y-%m-%dT%H:%M:%S.') + fsec[:3] +
'Z'))
148 errorAt =
"scene_center_time"
149 scene_center_str = globalAttr.get(
"Scene Center Time")
150 scd = node_crossing_str[:8]
151 sct = node_crossing_str[9:17]
152 fsec = node_crossing_str[18:21]
153 scene_center = datetime.strptime(scd+sct,
"%Y%m%d%H:%M:%S")
154 ncFile.setncattr(
"scene_center_time",
str(scene_center.strftime(
'%Y-%m-%dT%H:%M:%S.') + fsec[:3] +
'Z'))
157 errorAt =
"time_coverage_start"
158 stime_str = globalAttr.get(
"Start Time")
160 sct = stime_str[9:17]
161 fsec = stime_str[18:21]
162 stime = datetime.strptime(scd+sct,
"%Y%m%d%H:%M:%S")
163 ncFile.setncattr(
"time_coverage_start",
str(stime.strftime(
'%Y-%m-%dT%H:%M:%S.') + fsec[:3] +
'Z'))
166 errorAt =
"time_coverage_end"
167 etime_str = globalAttr.get(
"End Time")
169 ect = etime_str[9:17]
170 fsec = etime_str[18:21]
171 etime = datetime.strptime(ecd+ect,
"%Y%m%d%H:%M:%S")
172 ncFile.setncattr(
"time_coverage_end",
str(etime.strftime(
'%Y-%m-%dT%H:%M:%S.') + fsec[:3] +
'Z'))
175 ncFile.setncattr(
"l1aconvert_octs_version", __version__)
179 ncFile.setncattr(
"history",
"{}; python3 l1acovert_seawifs {} {}".format(globalAttr.get(
"Processing Control")[:-1], iFile, oFile))
182 errorAt =
"date_created"
183 create_date = datetime.now(tz=timezone.utc)
185 ncFile.setncattr(
"date_created",
str(create_date.strftime(
'%Y-%m-%dT%H:%M:%SZ')))
188 ncFile.setncattr(
"cdm_data_type",
"swath")
189 ncFile.setncattr(
"Conventions",
"CF-1.8, ACDD-1.3")
190 ncFile.setncattr(
"creator_email",
"data@oceancolor.gsfc.nasa.gov")
191 ncFile.setncattr(
"creator_name",
"NASA/GSFC/OBPG")
192 ncFile.setncattr(
"creator_url",
"https://oceancolor.gsfc.nasa.gov")
193 ncFile.setncattr(
"institution",
"NASA Goddard Space Flight Center, Ocean Biology Processing Group")
194 ncFile.setncattr(
"instrument",
"OCTS")
195 ncFile.setncattr(
"keywords_vocabulary",
"NASA Global Change Master Directory (GCMD) Science Keywords")
196 ncFile.setncattr(
"license",
"https://www.earthdata.nasa.gov/engage/open-data-services-and-software/data-and-information-policy")
197 ncFile.setncattr(
"naming_authority",
"gov.nasa.gsfc.oceancolor")
198 ncFile.setncattr(
"platform",
"ADEOS")
199 ncFile.setncattr(
"processing_level",
"L1A")
200 ncFile.setncattr(
"processing_version",
"V2")
201 ncFile.setncattr(
"product_name",
"{}".format(oFile.name))
202 ncFile.setncattr(
"project",
"Ocean Biology Processing Group")
203 ncFile.setncattr(
"publisher_email",
"data@oceancolor.gsfc.nasa.gov")
204 ncFile.setncattr(
"publisher_name",
"NASA/GSFC/OB.DAAC")
205 ncFile.setncattr(
"publisher_url",
"https://oceancolor.gsfc.nasa.gov")
206 ncFile.setncattr(
"standard_name_vocabulary",
"CF Standard Name Table v79")
209 print(f
"-E- Error copying global attributes. Was processing <{errorAt}> from HDF4 when error was caught.")
218 for attr
in attrDict.keys():
219 if (attr ==
"long_name" or attr ==
"long name"):
220 ncVar.long_name = attrDict.get(attr)
221 if (attr ==
"units"):
222 if ncVar.name !=
'l1a_data':
223 ncVar.units = attrDict.get(attr)
226 if (attr ==
"valid_range" or attr ==
"valid range"):
229 validRange = attrDict.get(attr)
234 if isinstance(validRange, str):
235 validRange = attrDict.get(attr)[1:-2].split(
",")
240 if (dataType == np.float32):
241 npValidMin = np.float32(validRange[0])
242 npValidMax = np.float32(validRange[1])
244 elif (dataType == np.float64):
245 npValidMin = np.float64(validRange[0])
246 npValidMax = np.float64(validRange[1])
248 elif (dataType == np.uint16):
249 npValidMin = np.uint16(validRange[0])
250 npValidMax = np.uint16(validRange[1])
252 elif (dataType == np.uint32):
253 npValidMin = np.uint32(validRange[0])
254 npValidMax = np.uint32(validRange[1])
256 elif (dataType == np.int16):
257 npValidMin = np.int16(validRange[0])
258 npValidMax = np.int16(validRange[1])
261 npValidMin = np.int32(validRange[0])
262 npValidMax = np.int32(validRange[1])
265 ncVar.valid_min = npValidMin
266 ncVar.valid_max = npValidMax
313 dimsize = len(ncFile.dimensions[dim])
314 if sizes[dim] <= dimsize:
315 chunks.append(sizes[dim])
335 print(
"Copying datasets/variables...")
337 datasetNames = hdfFile.datasets().keys()
338 for name
in datasetNames:
344 currSet = hdfFile.select(name)
345 hdfDataType = currSet.info()[3];
346 hdfDims = currSet.dimensions().keys()
348 hdfDatasetAttr = currSet.attributes()
353 ncDatatype = GET_NC_TYPES.get(hdfDataType)
359 newVariable = ncFile.createVariable(
convertToNetcdfName(name), ncDatatype, hdfDims, chunksizes=chunks, zlib=
True)
360 newVariable[:] = data
366 newVariable.setncattr(
"units",
"milliseconds")
367 newVariable.setncattr(
"comment",
"milliseconds since start of day, day boundary crossing indicated if value is smaller than previous scan")
369 except Exception
as e:
370 print(f
"-E- Error copying datasets/variables. Error occurred with HDF4 dataset named <{errorAt}>")
371 print(f
"Reason: {e}")
378 print(
"adding CF-compliant time variable...")
380 msecs = ncFile[
'msec'][:]
381 timevar = np.zeros(len(msecs),np.float64)
382 time_coverage_start = datetime.fromisoformat(ncFile.time_coverage_start[:23])
383 day_start = datetime.strptime(time_coverage_start.strftime(
'%Y%m%d'),
'%Y%m%d')
384 for i,msec
in enumerate(msecs):
385 seconds = msec / 1000.
386 scantime = day_start + timedelta(seconds=seconds)
387 if scantime < time_coverage_start:
388 scantime = scantime+ timedelta(days=1)
389 timevar[i] = scantime.strftime(
'%s.%f')
391 if len(ncFile.dimensions[
'rec']) < chunksize[0]:
393 timeVariable = ncFile.createVariable(
'time', np.float64,
'rec', chunksizes=chunksize, zlib=
True)
394 timeVariable[:] = timevar
395 timeVariable.setncattr(
"long_name",
"time")
396 timeVariable.setncattr(
"units",
"seconds since 1970-1-1")
398 except Exception
as e:
399 print(f
"-E- Error occurred with adding time dataset...")
400 print(f
"Reason: {e}")
408 print(
"Closing HDF4 File...")
410 print(
"Closing NetCDF File...")
422 errorAt =
"Setting Dimensions"
426 dimSet.add((
"tilts", 1))
427 dimSet.add((
"bands", 8))
428 dimSet.add((
"bbt", 5))
429 dimSet.add((
"ndatas", 6 ))
433 dimSet.add((
"ndatas12", 72))
435 dimSet.add((
"nrec", 1))
436 dimSet.add((
"ntilt", 3))
437 dimSet.add((
"gains", 4))
438 dimSet.add((
"reft", 1))
439 dimSet.add((
"ntbl", 1))
440 dimSet.add((
"pixels", 2222))
441 dimSet.add((
"err", 1))
442 dimSet.add((
"vec", 3))
443 dimSet.add((
"instt", 4))
444 dimSet.add((
"instr", 120))
445 dimSet.add((
"points", 1440))
447 dimSet.add((
"mat", 3))
448 dimSet.add((
"detectors", 10))
449 dimSet.add((
"odatas22", 22))
451 dimSet.add((
"tilts", 1))
452 dimSet.add((
"dets", 1))
453 dimSet.add((
"ninf", 1))
454 dimSet.add((
"rads", 1))
456 dimSet.add((
"coefs", 4))
458 dimSet.add((
"pref", 128))
459 dimSet.add((
"pairs", 2))
460 dimSet.add((
"flag", 24))
461 dimSet.add((
"sdet", 2))
462 dimSet.add((
"ndatas22", 132))
463 dimSet.add((
"ndatas14", 84))
464 dimSet.add((
"bdatas", 2))
465 dimSet.add((
"odatas", 1))
467 errorAt =
"extract dimension details from HDF file"
469 hdfDatasets = hdfFile.datasets()
477 errorAt = f
"extract {var} from HDF file"
478 varInfo = hdfDatasets[var]
479 dimNames = varInfo[0]
483 for i
in range(len(dimNames)):
492 errorAt = f
"set {dim} in the NetCDF file"
495 ncFile.createDimension(name, val)
498 print(f
"-E- Error copying dimensions. Was trying to {errorAt} when error was caught.")
502 print(f
"l1aconvert_octs {__version__}")
508 parser = argparse.ArgumentParser(description=
'''
509 Given an HDF4 Scientific Dataset (SD), convert it
510 into a NetCDF (NC) file format with the same name.
511 If the optional argument [oFile] name is given,
512 output as the oFile name.
515 parser.add_argument(
"ifile", type=str, help=
"input HDF4 File")
516 parser.add_argument(
"ofile", nargs=
'?', type=str, help=
"Optional output file name; default = input + '.nc'")
517 parser.add_argument(
"--doi",
"-d",type=str,default=
None,help=
"DOI string for metadata")
521 args = parser.parse_args()
522 fileName = args.ifile
523 oFileName = fileName +
".nc" if args.ofile
is None else args.ofile
524 oFileName = Path(oFileName)
526 print(f
"Input File:\t{fileName}")
527 print(f
"Output File:\t{oFileName}\n")
531 hdfFile = HDF(fileName, HDF_READ)
532 print(f
"Opening file:\t{fileName}")
534 print(f
"\n-E- Error opening file named: {fileName}.\n Make sure the filetype is hdf4.\n")
538 ncFile =
NC(oFileName, NC_WRITE, NC_FORMAT)
542 ncFile.setncattr(
"doi",args.doi)
546 print(
"Conversion of L1A HDF4 to NETCDF successful")
548 if __name__ ==
'__main__':