IODA Bundle
giirs_lw2ioda.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 #
4 # (C) Copyright 2019 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 from __future__ import print_function
11 import sys
12 import argparse
13 import numpy as np
14 from datetime import datetime, timedelta
15 import netCDF4 as nc
16 import re
17 import dateutil.parser
18 from pathlib import Path
19 
20 IODA_CONV_PATH = Path(__file__).parent/"@SCRIPT_LIB_PATH@"
21 if not IODA_CONV_PATH.is_dir():
22  IODA_CONV_PATH = Path(__file__).parent/'..'/'lib-python'
23 sys.path.append(str(IODA_CONV_PATH.resolve()))
24 
25 import ioda_conv_ncio as iconv
26 from orddicts import DefaultOrderedDict
27 
28 
29 vName = "longwave_radiance"
30 
31 locationKeyList = [
32  ("latitude", "float"),
33  ("longitude", "float"),
34  ("datetime", "string")
35 ]
36 
37 LocMdata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
38 VarMdata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
39 AttrData = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
40 
41 
42 class LwRadiance(object):
43  def __init__(self, filenames, writer):
44  self.filenamesfilenames = filenames
45  self.datadata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
46  self.units_valuesunits_values = {} # cannot use an ordered dict for this
47  self.writerwriter = writer
48  self.readInFilesreadInFiles()
49 
50  def xferMdataVar(self, ncd, inName, outName, mData, dType):
51  # This method adds a variable to the given metadata dictionary element.
52  ncVar = ncd.variables[inName]
53  mData[outName] = self.writerwriter.FillNcVector(ncVar[:], dType)
54  if 'units' in ncVar.ncattrs():
55  self.units_valuesunits_values[outName] = ncVar.getncattr('units')
56 
57  def readInFiles(self):
58  # This method will transfer variable data from the input file into a
59  # dictionary (self.data) that the netcdf writer understands.
60  #
61  # The mapping from input variables to ioda variables for IR LW radiance is:
62  #
63  # dimensions:
64  # LWchannel -> nvars
65  # LWdetector -> nlocs
66  #
67  # global attributes:
68  # 'Observing Beginning Date' -> YYYY-MM-DD
69  # 'Observing Beginning Time' -> HH:MM:SS
70  # The two above attributes get translated to a single datetime value
71  # which becomes the value for each location in datetime@MetaData.
72  # The seconds value is a float number possibly containing fractional seconds.
73  #
74  # variables:
75  # LW_wnum(LWchannel) -> channel_wavenumber@VarMetaData
76  # Number the channels starting with 1 assuming that the order
77  # in variables indexed by LWchannel are sequential channel numbers (1..n).
78  #
79  # IRLW_Latitude(LWdetector) -> latitude@MetaData
80  # IRLW_Longitude(LWdetector) -> longitude@MetaData
81  # IRLW_SolarZenith(LWdetector) -> solar_zenith_angle@MetaData
82  # IRLW_SolarAzimuth(LWdetector) -> solar_azimuth_angle@MetaData
83  # IRLW_SatelliteZenith(LWdetector) -> sensor_zenith_angle@MetaData
84  # IRLW_SatelliteAzimuth(LWdetector) -> sensor_azimuth_angle@MetaData
85  #
86  # ES_RealLW(LWchannel, Lwdetector) -> longwave_radiance_#@ObsValue
87  # For now, fabricate the QC marks and error esitmate by setting all
88  # locations to 0 for longwave_radiance_#@PreQC and setting all locations
89  # to 2.0 for longwave_radiance_#@ObsError.
90  valKey = vName, self.writerwriter.OvalName()
91  errKey = vName, self.writerwriter.OerrName()
92  qcKey = vName, self.writerwriter.OqcName()
93 
94  for f in self.filenamesfilenames:
95  ncd = nc.Dataset(f, 'r')
96 
97  # Get the number of locations and channels
98  nlocs = ncd.dimensions["LWdetector"].size
99  nchans = ncd.dimensions["LWchannel"].size
100  self.writerwriter._nlocs = nlocs
101  self.writerwriter._nvars = nchans
102 
103  # Channel metadata associated with longwave radiance
104  self.xferMdataVarxferMdataVar(ncd, 'LW_wnum', 'channel_wavenumber', VarMdata, 'float')
105 
106  VarMdata['channel_number'] = self.writerwriter.FillNcVector(
107  np.ma.array(range(1, nchans+1)), 'integer')
108  self.units_valuesunits_values['channel_number'] = '1'
109 
110  # The file contains a single image with a sub-second scan time. We
111  # are currently retaining date-time stamps to the nearest second so
112  # for now just grab the beginning scan time and use that for all locations.
113  # The variables holding the time offset from 1 Jan, 0000 are problematic
114  # since the python datetime package doesn't allow year zero, and it's not
115  # clear how days are coverted over several millinnea. The observation
116  # date and time are also in global attributes and appear to line up with
117  # the file name.
118  obsDate = ncd.getncattr("Observing Beginning Date").split("-")
119  obsTime = ncd.getncattr("Observing Beginning Time").split(":")
120  obsSeconds = timedelta(seconds=int(round(float(obsTime[2]))))
121  obsDateTime = datetime(year=int(obsDate[0]), month=int(obsDate[1]),
122  day=int(obsDate[2]), hour=int(obsTime[0]),
123  minute=int(obsTime[1])) + obsSeconds
124  obsDtimeString = obsDateTime.strftime("%Y-%m-%dT%H:%M:%SZ")
125 
126  # Form a vector nlocs long containing the datetime stamp
127  LocMdata['datetime'] = self.writerwriter.FillNcVector(
128  np.full((nlocs), obsDtimeString), 'datetime')
129  self.units_valuesunits_values['datetime'] = 'ISO 8601 format'
130 
131  # Read in the latitude and longitude associated with the long wave data
132  self.xferMdataVarxferMdataVar(ncd, 'IRLW_Latitude', 'latitude', LocMdata, 'float')
133  self.xferMdataVarxferMdataVar(ncd, 'IRLW_Longitude', 'longitude', LocMdata, 'float')
134 
135  # Read in the instrument meta data associated with the long wave data
136  self.xferMdataVarxferMdataVar(ncd, 'IRLW_SolarZenith', 'solar_zenith_angle', LocMdata, 'float')
137  self.xferMdataVarxferMdataVar(ncd, 'IRLW_SolarAzimuth', 'solar_azimuth_angle', LocMdata, 'float')
138  self.xferMdataVarxferMdataVar(ncd, 'IRLW_SatelliteZenith', 'sensor_zenith_angle', LocMdata, 'float')
139  self.xferMdataVarxferMdataVar(ncd, 'IRLW_SatelliteAzimuth', 'sensor_azimuth_angle', LocMdata, 'float')
140 
141  # Read in the long wave radiance
142  # For now fabricate the QC marks and error estimates
143  ncVar = ncd.variables['ES_RealLW']
144  lwRadiance = ncVar[:]
145  if 'units' in ncVar.ncattrs():
146  Units = ncVar.getncattr('units')
147  else:
148  Units = None
149  for ivar in range(nchans):
150  varName = "longwave_radiance_%d" % (ivar + 1)
151  self.datadata[(varName, 'ObsValue')] = self.writerwriter.FillNcVector(
152  lwRadiance[ivar, :], 'float')
153  self.datadata[(varName, 'PreQC')] = self.writerwriter.FillNcVector(
154  np.full((nlocs), 0), 'integer')
155  self.datadata[(varName, 'ObsError')] = self.writerwriter.FillNcVector(
156  np.full((nlocs), 2.0), 'float')
157  if Units:
158  self.units_valuesunits_values[varName] = Units
159 
160  ncd.close()
161 
162 
163 def main():
164 
165  parser = argparse.ArgumentParser(
166  description=(
167  'Read NSMC GIIRS long wave radiance file(s) and convert'
168  ' to a concatenated IODA formatted output file.')
169  )
170  required = parser.add_argument_group(title='required arguments')
171  required.add_argument(
172  '-i', '--input',
173  help="name of giirs input file(s)",
174  type=str, nargs='+', required=True)
175  required.add_argument(
176  '-o', '--output',
177  help="name of ioda output file",
178  type=str, required=True)
179  required.add_argument(
180  '-d', '--date',
181  help="base date for the center of the window",
182  metavar="YYYYMMDDHH", type=str, required=True)
183  args = parser.parse_args()
184  fdate = datetime.strptime(args.date, '%Y%m%d%H')
185 
186  writer = iconv.NcWriter(args.output, [], locationKeyList)
187 
188  # Read in the giirs lw radiance
189  lwrad = LwRadiance(args.input, writer)
190 
191  # Add the 'date_time_string' attribute which sets the reference datetime for
192  # the observations.
193  AttrData['date_time_string'] = fdate.strftime("%Y-%m-%dT%H:%M:%SZ")
194 
195  # Write the obs data and meta data into the ioda file
196  writer.BuildNetcdf(lwrad.data, LocMdata, VarMdata, AttrData, lwrad.units_values)
197 
198 
199 if __name__ == '__main__':
200  main()
def xferMdataVar(self, ncd, inName, outName, mData, dType)
def __init__(self, filenames, writer)