IODA Bundle
tropomi_no2_nc2ioda.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 #
4 # (C) Copyright 2020 UCAR
5 #
6 # This software is licensed under the terms of the Apache Licence Version 2.0
7 # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
8 #
9 
10 import sys
11 import argparse
12 import netCDF4 as nc
13 import numpy as np
14 from datetime import datetime, timedelta
15 import os
16 from pathlib import Path
17 
18 IODA_CONV_PATH = Path(__file__).parent/"@SCRIPT_LIB_PATH@"
19 if not IODA_CONV_PATH.is_dir():
20  IODA_CONV_PATH = Path(__file__).parent/'..'/'lib-python'
21 sys.path.append(str(IODA_CONV_PATH.resolve()))
22 
23 import ioda_conv_ncio as iconv
24 from collections import defaultdict, OrderedDict
25 from orddicts import DefaultOrderedDict
26 
27 locationKeyList = [
28  ("latitude", "float"),
29  ("longitude", "float"),
30  ("datetime", "string"),
31 ]
32 
33 obsvars = {
34  'nitrogendioxide_tropospheric_column': 'nitrogen_dioxide_in_tropospheric_column',
35  'nitrogendioxide_total_column': 'nitrogen_dioxide_in_total_column',
36 }
37 
38 AttrData = {
39  'converter': os.path.basename(__file__),
40 }
41 
42 
43 class tropomi(object):
44  def __init__(self, filenames, writer):
45  self.filenamesfilenames = filenames
46  self.writerwriter = writer
47  self.varDictvarDict = defaultdict(lambda: defaultdict(dict))
48  self.outdataoutdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
49  self.loc_mdataloc_mdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
50  self.var_mdatavar_mdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
51  self.unitsunits = {}
52  self._read_read()
53 
54  # Open input file and read relevant info
55  def _read(self):
56  # set up variable names for IODA
57  for iodavar in ['nitrogen_dioxide_in_tropospheric_column', 'nitrogen_dioxide_in_total_column']:
58  self.varDictvarDict[iodavar]['valKey'] = iodavar, self.writerwriter.OvalName()
59  self.varDictvarDict[iodavar]['errKey'] = iodavar, self.writerwriter.OerrName()
60  self.varDictvarDict[iodavar]['qcKey'] = iodavar, self.writerwriter.OqcName()
61  self.unitsunits[iodavar] = 'mol m-2'
62  # loop through input filenames
63  first = True
64  for f in self.filenamesfilenames:
65  ncd = nc.Dataset(f, 'r')
66  # get global attributes
67  AttrData['date_time_string'] = ncd.getncattr('time_reference')[0:19]+'Z'
68  AttrData['sensor'] = ncd.getncattr('sensor')
69  AttrData['platform'] = ncd.getncattr('platform')
70  # many variables are time, scanline, ground_pixel
71  # but others are just time, scanline
72  lats = ncd.groups['PRODUCT'].variables['latitude'][:].ravel()
73  lons = ncd.groups['PRODUCT'].variables['longitude'][:].ravel()
74  qa_value = ncd.groups['PRODUCT'].variables['qa_value'][:] # 2D
75  times = np.empty_like(qa_value, dtype=object)
76  qa_value = qa_value.ravel()
77  qc_flag = ncd.groups['PRODUCT'].groups['SUPPORT_DATA'].groups['DETAILED_RESULTS']\
78  .variables['processing_quality_flags'][:]
79  qc_flag = qc_flag.ravel().astype('int32')
80  time1 = ncd.groups['PRODUCT'].variables['time_utc'][:]
81  for t in range(len(time1[0])):
82  times[0, t, :] = time1[0, t][0:19]+'Z'
83  times = times.ravel()
84  # need additional variable to use the averaging kernel for DA
85  kernel_err = ncd.groups['PRODUCT'].\
86  variables['nitrogendioxide_tropospheric_column_precision_kernel'][:].ravel()
87  kernel_err_total = ncd.groups['PRODUCT'].groups['SUPPORT_DATA'].groups['DETAILED_RESULTS'].\
88  variables['nitrogendioxide_total_column_precision_kernel'][:].ravel()
89  trop_layer = ncd.groups['PRODUCT'].variables['tm5_tropopause_layer_index'][:].ravel()
90  total_airmass = ncd.groups['PRODUCT'].variables['air_mass_factor_total'][:].ravel()
91  trop_airmass = ncd.groups['PRODUCT'].\
92  variables['air_mass_factor_troposphere'][:].ravel()
93  # grab the averaging kernel
94  avg_kernel = ncd.groups['PRODUCT'].variables['averaging_kernel'][:]
95  nlevs = len(avg_kernel[0, 0, 0])
96  AttrData['averaging_kernel_levels'] = np.int32(nlevs)
97  if first:
98  self.loc_mdataloc_mdata['datetime'] = self.writerwriter.FillNcVector(times, "datetime")
99  self.loc_mdataloc_mdata['latitude'] = lats
100  self.loc_mdataloc_mdata['longitude'] = lons
101  self.loc_mdataloc_mdata['quality_assurance_value'] = qa_value
102  self.loc_mdataloc_mdata['troposphere_layer_index'] = trop_layer
103  self.loc_mdataloc_mdata['air_mass_factor_total'] = total_airmass
104  self.loc_mdataloc_mdata['air_mass_factor_troposphere'] = trop_airmass
105  self.loc_mdataloc_mdata['tropospheric_averaging_kernel_precision'] = kernel_err
106  self.loc_mdataloc_mdata['averaging_kernel_precision'] = kernel_err_total
107  for k in range(nlevs):
108  varname = 'averaging_kernel_level_'+str(k+1)
109  self.loc_mdataloc_mdata[varname] = avg_kernel[..., k].ravel()
110  else:
111  self.loc_mdataloc_mdata['datetime'] = np.concatenate((self.loc_mdataloc_mdata['datetime'],
112  self.writerwriter.FillNcVector(times, "datetime")))
113  self.loc_mdataloc_mdata['latitude'] = np.concatenate((self.loc_mdataloc_mdata['latitude'], lats))
114  self.loc_mdataloc_mdata['longitude'] = np.concatenate((self.loc_mdataloc_mdata['longitude'], lons))
115  self.loc_mdataloc_mdata['quality_assurance_value'] = np.concatenate((
116  self.loc_mdataloc_mdata['quality_assurance_value'], qa_value))
117  self.loc_mdataloc_mdata['troposphere_layer_index'] = np.concatenate((
118  self.loc_mdataloc_mdata['troposphere_layer_index'], trop_layer))
119  self.loc_mdataloc_mdata['air_mass_factor_total'] = np.concatenate((
120  self.loc_mdataloc_mdata['air_mass_factor_total'], total_airmass))
121  self.loc_mdataloc_mdata['air_mass_factor_troposphere'] = np.concatenate((
122  self.loc_mdataloc_mdata['air_mass_factor_troposphere'], trop_airmass))
123  self.loc_mdataloc_mdata['tropospheric_averaging_kernel_precision'] = np.concatenate((
124  self.loc_mdataloc_mdata['tropospheric_averaging_kernel_precision'], kernel_err))
125  self.loc_mdataloc_mdata['averaging_kernel_precision'] = np.concatenate((
126  self.loc_mdataloc_mdata['averaging_kernel_precision'], kernel_err_total))
127  for k in range(nlevs):
128  varname = 'averaging_kernel_level_'+str(k+1)
129  self.loc_mdataloc_mdata[varname] = np.concatenate((self.loc_mdataloc_mdata[varname],
130  avg_kernel[..., k].ravel()))
131  for ncvar, iodavar in obsvars.items():
132  if ncvar in ['nitrogendioxide_tropospheric_column']:
133  data = ncd.groups['PRODUCT'].variables[ncvar][:].ravel()
134  err = ncd.groups['PRODUCT'].variables[ncvar+'_precision'][:].ravel()
135  else:
136  data = ncd.groups['PRODUCT'].groups['SUPPORT_DATA'].groups['DETAILED_RESULTS'].variables[ncvar][:].ravel()
137  err = ncd.groups['PRODUCT'].groups['SUPPORT_DATA'].groups['DETAILED_RESULTS'].variables[ncvar+'_precision'][:].ravel()
138  if first:
139  self.outdataoutdata[self.varDictvarDict[iodavar]['valKey']] = data
140  self.outdataoutdata[self.varDictvarDict[iodavar]['errKey']] = err
141  self.outdataoutdata[self.varDictvarDict[iodavar]['qcKey']] = qc_flag
142  else:
143  self.outdataoutdata[self.varDictvarDict[iodavar]['valKey']] = np.concatenate(
144  (self.outdataoutdata[self.varDictvarDict[iodavar]['valKey']], data))
145  self.outdataoutdata[self.varDictvarDict[iodavar]['errKey']] = np.concatenate(
146  (self.outdataoutdata[self.varDictvarDict[iodavar]['errKey']], err))
147  self.outdataoutdata[self.varDictvarDict[iodavar]['qcKey']] = np.concatenate(
148  (self.outdataoutdata[self.varDictvarDict[iodavar]['qcKey']], qc_flag))
149  first = False
150  self.writerwriter._nvars = len(obsvars)
151  self.writerwriter._nlocs = len(self.loc_mdataloc_mdata['datetime'])
152 
153 
154 def main():
155 
156  # get command line arguments
157  parser = argparse.ArgumentParser(
158  description=(
159  'Reads TROPOMI NO2 netCDF files provided by NESDIS'
160  'and converts into IODA formatted output files. Multiple'
161  'files are able to be concatenated.')
162  )
163 
164  required = parser.add_argument_group(title='required arguments')
165  required.add_argument(
166  '-i', '--input',
167  help="path of TROPOMI L2 NO2 observation netCDF input file(s)",
168  type=str, nargs='+', required=True)
169  required.add_argument(
170  '-o', '--output',
171  help="path of IODA output file",
172  type=str, required=True)
173 
174  args = parser.parse_args()
175 
176  # setup the IODA writer
177  writer = iconv.NcWriter(args.output, locationKeyList)
178 
179  # Read in the NO2 data
180  no2 = tropomi(args.input, writer)
181 
182  # write everything out
183  writer.BuildNetcdf(no2.outdata, no2.loc_mdata, no2.var_mdata, AttrData, no2.units)
184 
185 
186 if __name__ == '__main__':
187  main()
def __init__(self, filenames, writer)