IODA Bundle
smap_sss2ioda.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 import sys
11 import argparse
12 import numpy as np
13 from datetime import datetime, timedelta
14 import netCDF4 as nc
15 import re
16 import dateutil.parser
17 from pathlib import Path
18 
19 IODA_CONV_PATH = Path(__file__).parent/"@SCRIPT_LIB_PATH@"
20 if not IODA_CONV_PATH.is_dir():
21  IODA_CONV_PATH = Path(__file__).parent/'..'/'lib-python'
22 sys.path.append(str(IODA_CONV_PATH.resolve()))
23 
24 import ioda_conv_ncio as iconv
25 from orddicts import DefaultOrderedDict
26 
27 
28 vName = "sea_surface_salinity"
29 
30 locationKeyList = [
31  ("latitude", "float"),
32  ("longitude", "float"),
33  ("datetime", "string")
34 ]
35 
36 AttrData = {
37  'odb_version': 1,
38 }
39 
40 
41 class Salinity(object):
42  def __init__(self, filenames, date, writer):
43  self.filenamesfilenames = filenames
44  self.datedate = date
45  self.datadata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
46  self.writerwriter = writer
47  self._read_read()
48 
49  # Open obs file and read/load relevant info
50  def _read(self):
51  valKey = vName, self.writerwriter.OvalName()
52  errKey = vName, self.writerwriter.OerrName()
53  qcKey = vName, self.writerwriter.OqcName()
54 
55  for f in self.filenamesfilenames:
56  print(" Reading file: ", f)
57  ncd = nc.Dataset(f, 'r')
58 
59  # determine if this is JPL or RSS file.
60  # the variable names depend on which type of file it is.
61  source = ncd.institution
62  if re.search("^Remote Sensing Systems.*", source) is not None:
63  source = 'RSS'
64  source_var_name = {
65  'time': 'time',
66  'lat': 'cellat',
67  'lon': 'cellon',
68  'sss': 'sss_smap',
69  'sss_qc': 'iqc_flag',
70  }
71  elif re.search("^JPL.*", source) is not None:
72  source = 'JPL'
73  source_var_name = {
74  'time': 'row_time',
75  'lat': 'lat',
76  'lon': 'lon',
77  'sss': 'smap_sss',
78  'sss_err': 'smap_sss_uncertainty',
79  'sss_qc': 'quality_flag'
80  }
81  else:
82  print("Error: unknown source: " + source)
83  print("Only JPL or RSS sources are handled")
84  sys.exit(1)
85 
86  # make sure this is lvl 2
87  # TODO: handle L3 files at somepoint?)
88  if ncd.processing_level[:2] != 'L2':
89  print("Error: only L2 files handled for now.")
90  sys.exit(1)
91 
92  # get base date for file
93  s = ' '.join(
94  ncd.variables[source_var_name['time']].units.split(' ')[2:3])
95  basetime = dateutil.parser.parse(s)
96 
97  # read in the fields
98  data = {}
99  for v in source_var_name:
100  if v == 'sss_qc':
101  data[v] = ncd.variables[source_var_name[v]][:].flatten().astype(int)
102  else:
103  data[v] = ncd.variables[source_var_name[v]][:].flatten()
104 
105  # JPL files have a time for each row,
106  # RSS has time for each cell, account for this
107  if source == 'JPL':
108  col = len(data['lon']) / len(data['time'])
109  data['time'] = np.tile(
110  np.array(data['time']), (int(col), 1)).T.flatten()
111 
112  # remove masked gridpoints
113  mask = np.logical_not(data['sss'].mask)
114  for v in source_var_name:
115  data[v] = data[v][mask]
116 
117  # for each observation
118  for i in range(len(data['time'])):
119  obs_date = basetime + timedelta(seconds=float(data['time'][i]))
120  locKey = data['lat'][i], data['lon'][i], obs_date.strftime(
121  "%Y-%m-%dT%H:%M:%SZ")
122  self.datadata[0][locKey][valKey] = data['sss'][i]
123  # if source == 'JPL': #RTOFS-DA
124  # if data['sss_qc'][i] <= 4:
125  # data['sss_qc'][i] = 0
126  # else:
127  # data['sss_qc'][i] = 1
128  self.datadata[0][locKey][qcKey] = data['sss_qc'][i]
129  if 'sss_err' in data:
130  self.datadata[0][locKey][errKey] = data['sss_err'][i]
131  else:
132  self.datadata[0][locKey][errKey] = 1.0
133  ncd.close()
134 
135 
136 def main():
137 
138  parser = argparse.ArgumentParser(
139  description=(
140  'Read JPL/RSS SMAP sea surface salinity (SSS) file(s) and convert'
141  ' to a concatenated IODA formatted output file.')
142  )
143  required = parser.add_argument_group(title='required arguments')
144  required.add_argument(
145  '-i', '--input',
146  help="name of sss input file(s)",
147  type=str, nargs='+', required=True)
148  required.add_argument(
149  '-o', '--output',
150  help="name of ioda output file",
151  type=str, required=True)
152  required.add_argument(
153  '-d', '--date',
154  help="base date for the center of the window",
155  metavar="YYYYMMDDHH", type=str, required=True)
156  args = parser.parse_args()
157  fdate = datetime.strptime(args.date, '%Y%m%d%H')
158 
159  writer = iconv.NcWriter(args.output, locationKeyList)
160 
161  # Read in the salinity
162  sal = Salinity(args.input, fdate, writer)
163 
164  # write them out
165  AttrData['date_time_string'] = fdate.strftime("%Y-%m-%dT%H:%M:%SZ")
166 
167  (ObsVars, LocMdata, VarMdata) = writer.ExtractObsData(sal.data)
168  writer.BuildNetcdf(ObsVars, LocMdata, VarMdata, AttrData)
169 
170 
171 if __name__ == '__main__':
172  main()
def __init__(self, filenames, date, writer)