IODA Bundle
modis_aod2ioda.py
Go to the documentation of this file.
1 #!/usr/bin/env python
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, date, 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 
34 obsvars = {
35  'A': "aerosol_optical_depth",
36 }
37 
38 AttrData = {
39  'converter': os.path.basename(__file__),
40 }
41 
42 
43 class AOD(object):
44  def __init__(self, filenames, obs_time, writer):
45  self.filenamesfilenames = filenames
46  self.obs_timeobs_time = obs_time
47  self.writerwriter = writer
48  self.varDictvarDict = defaultdict(lambda: defaultdict(dict))
49  self.outdataoutdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
50  self.loc_mdataloc_mdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
51  self.var_mdatavar_mdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
52  self.unitsunits = {}
53  self._read_read()
54 
55  def _read(self):
56  # set up variable names for IODA
57  for ncvar, iodavar in obsvars.items():
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 
62  # loop through input filenames
63  first = True
64  for f in self.filenamesfilenames:
65  ncd = nc.Dataset(f, 'r')
66  ncd_str = str(ncd)
67  if 'Terra' in ncd_str:
68  AttrData['platform'] = 'Terra'
69  elif 'Aqua' in ncd_str:
70  AttrData['platform'] = 'Aqua'
71  if 'MODIS' in ncd_str:
72  AttrData['sensor'] = 'v.modis_terra'
73 
74  obstime = self.obs_timeobs_time
75  AttrData['date_time'] = self.obs_timeobs_time
76  AttrData['observation_type'] = 'Aod'
77 
78  # Get variables
79  modis_time = ncd.variables['Scan_Start_Time'][:].ravel()
80 
81  # convert time to date_string
82  lats = ncd.variables['Latitude'][:].ravel()
83  lats = lats.astype('float32')
84  lons = ncd.variables['Longitude'][:].ravel()
85  lons = lons.astype('float32')
86  aod = ncd.variables['AOD_550_Dark_Target_Deep_Blue_Combined'][:].ravel()
87  land_sea_flag = ncd.variables['Land_sea_Flag'][:].ravel()
88  QC_flag = ncd.variables['Land_Ocean_Quality_Flag'][:].ravel()
89  QC_flag = QC_flag.astype('int8')
90  sol_zen = ncd.variables['Solar_Zenith'][:].ravel()
91  sen_zen = ncd.variables['Sensor_Zenith'][:].ravel()
92  unc_land = ncd.variables['Deep_Blue_Aerosol_Optical_Depth_550_Land_Estimated_Uncertainty'][:].ravel()
93 
94  # Remove undefined values
95  pos_index = np.where(aod > 0)
96  lats = lats[pos_index]
97  lons = lons[pos_index]
98  aod = aod[pos_index]
99  land_sea_flag = land_sea_flag[pos_index]
100  QC_flag = QC_flag[pos_index]
101  sol_zen = sol_zen[pos_index]
102  sen_zen = sen_zen[pos_index]
103  unc_land = unc_land[pos_index]
104  modis_time = modis_time[pos_index]
105  obs_time_2 = [datetime.fromisoformat('1993-01-01') + timedelta(seconds=x) for x in modis_time]
106  obs_time = [t.strftime('%Y-%m-%dT%H:%M:%SZ') for t in obs_time_2]
107 
108  # uncertainty estimates:
109  # From MODIS file (over ocean) and Levy, 2010 (over land)
110  # flag = 0 (ocean) 1(land) 2(coastal)
111 
112  over_ocean = np.logical_not(land_sea_flag > 0)
113  over_land = np.logical_not(land_sea_flag == 0)
114  UNC = np.where(over_land, unc_land, np.add(0.05, np.multiply(0.15, aod)))
115 
116  if first:
117  self.loc_mdataloc_mdata['latitude'] = lats
118  self.loc_mdataloc_mdata['longitude'] = lons
119  self.loc_mdataloc_mdata['datetime'] = self.writerwriter.FillNcVector(obs_time, "datetime")
120  else:
121  self.loc_mdataloc_mdata['latitude'] = np.concatenate((self.loc_mdataloc_mdata['latitude'], lats))
122  self.loc_mdataloc_mdata['longitude'] = np.concatenate((self.loc_mdataloc_mdata['longitude'], lons))
123  self.loc_mdataloc_mdata['datetime'] = np.concatenate((self.loc_mdataloc_mdata['datetime'], self.writerwriter.FillNcVector(obs_time, "datetime")))
124 
125  for ncvar, iodavar in obsvars.items():
126  data = aod.astype('float32')
127  err = UNC
128  if first:
129  self.outdataoutdata[self.varDictvarDict[iodavar]['valKey']] = data
130  self.outdataoutdata[self.varDictvarDict[iodavar]['errKey']] = err
131  self.outdataoutdata[self.varDictvarDict[iodavar]['qcKey']] = QC_flag
132 
133  else:
134  self.outdataoutdata[self.varDictvarDict[iodavar]['valKey']] = np.concatenate(
135  (self.outdataoutdata[self.varDictvarDict[iodavar]['valKey']], data))
136  self.outdataoutdata[self.varDictvarDict[iodavar]['errKey']] = np.concatenate(
137  (self.outdataoutdata[self.varDictvarDict[iodavar]['errKey']], err))
138  self.outdataoutdata[self.varDictvarDict[iodavar]['qcKey']] = np.concatenate(
139  (self.outdataoutdata[self.varDictvarDict[iodavar]['qcKey']], QC_flag))
140 
141  first = False
142  self.writerwriter._nvars = len(obsvars)
143  self.writerwriter._nlocs = len(self.loc_mdataloc_mdata['longitude'])
144 
145 
146 def main():
147 
148  # get command line arguments
149  # Usage: python blah.py -i /path/to/obs/2021060801.nc /path/to/obs/2021060802.nc ... -t Analysis_time /path/to/obs/2021060823.nc
150  # -o /path/to/ioda/20210608.nc
151  # where the input obs could be for any desired interval to concatenated together. Analysis time is generally the midpoint of
152  # analysis window.
153  parser = argparse.ArgumentParser(
154  description=(
155  'Reads MODIS AOD hdf4 files provided by NASA'
156  ' and converts into IODA formatted output files. Multiple'
157  ' files are able to be concatenated.')
158  )
159 
160  required = parser.add_argument_group(title='required arguments')
161  required.add_argument(
162  '-i', '--input',
163  help="path of MODIS AOD hdf4 input file(s)",
164  type=str, nargs='+', required=True)
165  required.add_argument(
166  '-t', '--time',
167  help="Observation time in global attributes (YYYYMMDDHH)",
168  type=int, 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  # get the obs time
180  obs_time = args.time
181 
182  # Read in the AOD data
183  aod_class = AOD(args.input, args.time, writer)
184  # write everything out
185  original_stdout = sys.stdout
186  writer.BuildNetcdf(aod_class.outdata, aod_class.loc_mdata, aod_class.var_mdata, AttrData)
187 
188 
189 if __name__ == '__main__':
190  main()
def __init__(self, filenames, obs_time, writer)