IODA Bundle
argoClim2ioda.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 #
4 # (C) Copyright 2019-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 re
13 import bisect
14 from datetime import datetime
15 import netCDF4 as nc4
16 import numpy as np
17 from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
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 class argoClim(object):
30 
31  def __init__(self, filename, begindate=None, enddate=None):
32 
33  self.filenamefilename = filename
34  self.begindatebegindate = begindate
35  self.enddateenddate = enddate
36 
37  # Read data from file
38  self._readData_readData()
39 
40  return
41 
42  def _readData(self):
43  '''
44  Read and store the data.
45  This file can be downloaded from:
46  http://www.argo.ucsd.edu/Gridded_fields.html
47  Global gridded NetCDF Argo only dataset produced by optimal interpolation
48  doi:10.1002/2017GL073426
49  '''
50 
51  try:
52  nc = nc4.Dataset(self.filenamefilename, 'r')
53  except IOError:
54  raise IOError('%s file not found!' % self.filenamefilename)
55  except Exception:
56  raise Exception('Unknown error opening %s' % self.filenamefilename)
57 
58  # First find the variable in this file
59  res = [x for x in list(nc.variables.keys()) if re.search("^ARGO_.*MEAN$", x)]
60 
61  if res:
62  self.varnamevarname = res[0].split('_')[1]
63  print("%s contains the variable: %s" % (self.filenamefilename, self.varnamevarname))
64  else:
65  raise NameError("No useable variable was found in the file %s" % self.filenamefilename)
66 
67  assert self.varnamevarname in ['TEMPERATURE', 'SALINITY'],\
68  "%s is not a valid variable name" % self.varnamevarname
69 
70  lon = nc.variables['LONGITUDE'][:]
71  lat = nc.variables['LATITUDE'][:]
72  pres = nc.variables['PRESSURE'][:]
73 
74  # Get absolute time instead of time since epoch
75  dtime = nc.variables['TIME']
76  time360day = nc4.num2date(dtime[:], dtime.units, calendar='360_day')
77 
78  # Convert Datetime360Day calendar to a regular datetime object
79  timeArray = np.array([datetime.strptime(x.strftime('%Y%m%d%H'), '%Y%m%d%H') for x in time360day])
80 
81  bI = 0
82  if self.begindatebegindate is not None and timeArray[0] <= self.begindatebegindate <= timeArray[-1]:
83  bI = bisect.bisect_left(timeArray, self.begindatebegindate)
84 
85  eI = -1
86  if self.enddateenddate is not None and timeArray[0] <= self.enddateenddate <= timeArray[-1]:
87  eI = bisect.bisect_left(timeArray, self.enddateenddate) + 1
88 
89  if bI == 0 and eI == -1: # all times
90  anomaly = nc.variables['ARGO_%s_ANOMALY' % self.varnamevarname][:]
91  time = timeArray[:]
92  elif bI == 0 and eI > -1: # upto a given time
93  anomaly = nc.variables['ARGO_%s_ANOMALY' % self.varnamevarname][:eI, :]
94  time = timeArray[:eI]
95  elif bI > 0 and eI == -1: # from a given time
96  anomaly = nc.variables['ARGO_%s_ANOMALY' % self.varnamevarname][bI:, :]
97  time = timeArray[bI:]
98  elif bI > 0 and eI > -1: # from and till given times
99  anomaly = nc.variables['ARGO_%s_ANOMALY' % self.varnamevarname][bI:eI, :]
100  time = timeArray[bI:eI]
101 
102  mean = nc.variables['ARGO_%s_MEAN' % self.varnamevarname][:]
103 
104  # create a full field from mean and anomaly
105  fullField = anomaly + np.tile(mean, (anomaly.shape[0], 1, 1, 1))
106 
107  try:
108  nc.close()
109  except IOError:
110  raise IOError('%s file could not be closed!' % self.filenamefilename)
111  except Exception:
112  raise Exception('Unknown error closing %s' % self.filenamefilename)
113 
114  # self.data is the data structure
115  self.datadata = {}
116  self.datadata['lat'] = lat
117  self.datadata['lon'] = lon
118  self.datadata['pres'] = pres
119  self.datadata['time'] = time
120  self.datadata['field'] = fullField.data
121 
122  return
123 
124 
125 class IODA(object):
126 
127  def __init__(self, filename, date, argo):
128  '''
129  Initialize IODA writer class,
130  transform to IODA data structure and,
131  write out to IODA file.
132  '''
133 
134  self.filenamefilename = filename
135  self.datedate = date
136 
137  self.locKeyListlocKeyList = [
138  ("latitude", "float"),
139  ("longitude", "float"),
140  ("pressure", "float"),
141  ("datetime", "string")
142  ]
143 
144  self.AttrDataAttrData = {
145  'odb_version': 1,
146  'date_time_string': self.datedate.strftime("%Y-%m-%dT%H:%M:%SZ")
147  }
148 
149  self.writerwriter = iconv.NcWriter(self.filenamefilename, self.locKeyListlocKeyList)
150 
151  # data is the dictionary containing IODA friendly data structure
152  self.datadata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
153 
154  recKey = 0
155  valKey = argo.varname, self.writerwriter.OvalName()
156  errKey = argo.varname, self.writerwriter.OerrName()
157  qcKey = argo.varname, self.writerwriter.OqcName()
158 
159  # There has to be a better way than explicitly looping over 4! dimensions
160  for t, time in enumerate(argo.data['time']):
161  for y, lat in enumerate(argo.data['lat']):
162  for x, lon in enumerate(argo.data['lon']):
163  for z, pres in enumerate(argo.data['pres']):
164 
165  locKey = lat, lon, pres, time.strftime('%Y-%m-%dT%H:%M:%SZ')
166 
167  val = argo.data['field'][t, z, y, x]
168  err = 0.
169  qc = 1
170 
171  self.datadata[recKey][locKey][valKey] = val
172  self.datadata[recKey][locKey][errKey] = err
173  self.datadata[recKey][locKey][qcKey] = qc
174 
175  recKey += 1
176 
177  (ObsVars, LocMdata, VarMdata) = self.writerwriter.ExtractObsData(self.datadata)
178  self.writerwriter.BuildNetcdf(ObsVars, LocMdata, VarMdata, self.AttrDataAttrData)
179 
180  return
181 
182 
183 def main():
184 
185  desc = 'Convert ARGO gridded global data to IODA netCDF4 format'
186  parser = ArgumentParser(
187  description=desc,
188  formatter_class=ArgumentDefaultsHelpFormatter)
189  parser.add_argument(
190  '-i', '--input', help='name of the Global ARGO file',
191  type=str, required=True)
192  parser.add_argument(
193  '-o', '--output', help='name of the output IODA netCDF ARGO file',
194  type=str, required=True, default=None)
195  parser.add_argument(
196  '-d', '--date', help='file date', metavar='YYYYMMDDHH',
197  type=str, required=True)
198  parser.add_argument(
199  '-b', '--begindate', help='end date for data time window', metavar='YYYYMMDDHH',
200  type=str, required=False, default=None)
201  parser.add_argument(
202  '-e', '--enddate', help='end date for data time window', metavar='YYYYMMDDHH',
203  type=str, required=False, default=None)
204 
205  args = parser.parse_args()
206 
207  filename = args.input
208  foutput = args.output
209  fdate = datetime.strptime(args.date, '%Y%m%d%H')
210  bdate = None if args.begindate is None else datetime.strptime(args.begindate, '%Y%m%d%H')
211  edate = None if args.enddate is None else datetime.strptime(args.enddate, '%Y%m%d%H')
212 
213  argo = argoClim(filename, begindate=bdate, enddate=edate)
214 
215  IODA(foutput, fdate, argo)
216 
217 
218 if __name__ == '__main__':
219  main()
def __init__(self, filename, date, argo)
def __init__(self, filename, begindate=None, enddate=None)