IODA Bundle
viirs_aod2ioda.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 #
3 # (C) Copyright 2020 UCAR
4 #
5 # This software is licensed under the terms of the Apache Licence Version 2.0
6 # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
7 #
8 
9 import sys
10 import argparse
11 import netCDF4 as nc
12 import numpy as np
13 from datetime import datetime, timedelta
14 from pathlib import Path
15 
16 IODA_CONV_PATH = Path(__file__).parent/"@SCRIPT_LIB_PATH@"
17 if not IODA_CONV_PATH.is_dir():
18  IODA_CONV_PATH = Path(__file__).parent/'..'/'lib-python'
19 sys.path.append(str(IODA_CONV_PATH.resolve()))
20 
21 import ioda_conv_ncio as iconv
22 from orddicts import DefaultOrderedDict
23 
24 vName = {
25  'A': "aerosol_optical_depth_4",
26 }
27 
28 locationKeyList = [
29  ("latitude", "float"),
30  ("longitude", "float"),
31  ("datetime", "string")
32 ]
33 
34 AttrData = {}
35 
36 
37 class AOD(object):
38 
39  def __init__(self, filename, method, mask, thin, writer):
40  self.filenamefilename = filename
41  self.methodmethod = method
42  self.maskmask = mask
43  self.thinthin = thin
44  self.datadata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
45  self.writerwriter = writer
46  self._read_read()
47 
48  def _read(self):
49  ncd = nc.Dataset(self.filenamefilename)
50  lons = ncd.variables['Longitude'][:].ravel()
51  lats = ncd.variables['Latitude'][:].ravel()
52  vals = ncd.variables['AOD550'][:].ravel()
53  errs = ncd.variables['Residual'][:].ravel()
54  qcpath = ncd.variables['QCPath'][:].ravel()
55  qcall = ncd.variables['QCAll'][:].ravel()
56  if self.maskmask == "maskout":
57  mask = np.logical_not(vals.mask)
58  vals = vals[mask]
59  lons = lons[mask]
60  lats = lats[mask]
61  errs = errs[mask]
62  qcpath = qcpath[mask]
63  qcall = qcall[mask]
64  # get global attributes
65  gatts = {attr: getattr(ncd, attr) for attr in ncd.ncattrs()}
66  base_datetime = gatts["time_coverage_end"]
67  self.satellitesatellite = gatts["satellite_name"]
68  self.sensorsensor = gatts["instrument_name"]
69  ncd.close()
70 
71  valKey = vName['A'], self.writerwriter.OvalName()
72  errKey = vName['A'], self.writerwriter.OerrName()
73  qcKey = vName['A'], self.writerwriter.OqcName()
74 
75  # apply thinning mask
76  if self.thinthin > 0.0:
77  mask_thin = np.random.uniform(size=len(lons)) > self.thinthin
78  lons = lons[mask_thin]
79  lats = lats[mask_thin]
80  vals = vals[mask_thin]
81  errs = errs[mask_thin]
82  qcpath = qcpath[mask_thin]
83  qcall = qcall[mask_thin]
84 
85  # set variable keys
86  sfcKey = "surface_type", "MetaData"
87  szaKey = "sol_zenith_angle", "MetaData"
88  saaKey = "sol_azimuth_angle", "MetaData"
89  mduKey = "modis_deep_blue_flag", "MetaData"
90  biaKey = "aerosol_optical_depth_4", "KnownObsBias"
91 
92  # defined surface type, uncertainty, and bias array
93  sfctyp = 0*qcall
94  uncertainty = 0.0*errs
95  uncertainty1 = 0.0*errs
96  uncertainty2 = 0.0*errs
97  bias = 0.0*errs
98  bias1 = 0.0*errs
99  bias2 = 0.0*errs
100 
101  if self.methodmethod == "nesdis":
102  # Case of water high quality
103  uncertainty = 0.00784394 + 0.219923*vals
104  bias = 0.0151799 + 0.0767385*vals
105  # case of bright land high quality
106  uncertainty1 = 0.0550472 + 0.299558*vals
107  bias1 = -0.0107621 + 0.150480*vals
108  # case of dark land high quality
109  uncertainty2 = 0.111431 + 0.128699*vals
110  bias2 = -0.0138969 + 0.157877*vals
111 
112  for i in range(len(lons)):
113 
114  # convert byte to integer
115  sfctyp[i] = int.from_bytes(qcpath[i], byteorder='big')
116  if self.methodmethod == "nesdis":
117  if sfctyp[i] == 1: # case of bright land high quality
118  bias[i] = bias1[i]
119  uncertainty[i] = uncertainty1[i]
120  else: # case of dark land high quality
121  bias[i] = bias2[i]
122  uncertainty[i] = uncertainty2[i]
123  errs[i] = uncertainty[i]
124 
125  locKey = lats[i], lons[i], base_datetime
126  self.datadata[0][locKey][valKey] = vals[i]
127  self.datadata[0][locKey][errKey] = errs[i]
128  self.datadata[0][locKey][qcKey] = qcall[i]
129  self.datadata[0][locKey][biaKey] = bias[i]
130 
131  # solar zenith angle (sza) is set all 0 for test
132  # solar azimuth angle (saa) is set all 0 for test
133  # modis_deep_blue_flag (mdu)is set all 0 for test
134  self.datadata[0][locKey][szaKey] = 0.0
135  self.datadata[0][locKey][saaKey] = 0.0
136  self.datadata[0][locKey][sfcKey] = sfctyp[i]
137  self.datadata[0][locKey][mduKey] = 0
138 
139  # write global attributes out
140  if self.satellitesatellite == 'NPP':
141  self.satellitesatellite = "suomi_npp"
142  if self.sensorsensor == 'VIIRS':
143  self.sensorsensor = "v.viirs-m_npp"
144 
145  AttrData["observation_type"] = "AOD"
146  AttrData["satellite"] = self.satellitesatellite
147  AttrData["sensor"] = self.sensorsensor
148  AttrData['date_time_string'] = base_datetime
149 
150 
151 def main():
152 
153  parser = argparse.ArgumentParser(
154  description=('Read VIIRS aerosol optical depth file(s) and Converter'
155  ' of native NetCDF format for observations of optical'
156  ' depth from VIIRS AOD550 to IODA netCDF format.')
157  )
158  parser.add_argument('-i', '--input',
159  help="name of viirs aod input file(s)",
160  type=str, required=True)
161  parser.add_argument('-o', '--output',
162  help="name of ioda output file",
163  type=str, required=True)
164  optional = parser.add_argument_group(title='optional arguments')
165  optional.add_argument(
166  '-m', '--method',
167  help="calculation bias/error method: nesdis/default, default=none",
168  type=str, required=True)
169  optional.add_argument(
170  '-k', '--mask',
171  help="maskout missing values: maskout/default, default=none",
172  type=str, required=True)
173  optional.add_argument(
174  '-t', '--thin',
175  help="percentage of random thinning, from 0.0 to 1.0. Zero indicates"
176  " no thinning is performed. (default: %(default)s)",
177  type=float, default=0.0)
178 
179  args = parser.parse_args()
180 
181  writer = iconv.NcWriter(args.output, locationKeyList)
182 
183  # Read in the profiles
184  aod = AOD(args.input, args.method, args.mask, args.thin, writer)
185 
186  (ObsVars, LocMdata, VarMdata) = writer.ExtractObsData(aod.data)
187 
188  # set constants for the four variables
189  VarMdata['frequency'] = np.full(1, 5.401666e+14, dtype='f4')
190  VarMdata['polarization'] = np.full(1, 1, dtype='i4')
191  VarMdata['wavenumber'] = np.full(1, 18161.61, dtype='f4')
192  VarMdata['sensor_channel'] = np.full(1, 4, dtype='i4')
193 
194  writer.BuildNetcdf(ObsVars, LocMdata, VarMdata, AttrData)
195 
196 
197 if __name__ == '__main__':
198  main()
def __init__(self, filename, method, mask, thin, writer)