IODA Bundle
afwa_snod2ioda.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 #
3 # (C) Copyright 2021 NOAA/NWS/NCEP/EMC
4 #
5 # This software is licensed under the terms of the Apache Licence Version 2.0
6 # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
7 #
8 import pygrib
9 import time, os, sys
10 import argparse
11 import netCDF4 as nc
12 import numpy as np
13 import pyproj
14 from datetime import datetime, timedelta
15 from pathlib import Path
16 
17 IODA_CONV_PATH = Path(__file__).parent/"@SCRIPT_LIB_PATH@"
18 if not IODA_CONV_PATH.is_dir():
19  IODA_CONV_PATH = Path(__file__).parent/'..'/'lib-python'
20 sys.path.append(str(IODA_CONV_PATH.resolve()))
21 
22 import ioda_conv_ncio as iconv
23 from collections import defaultdict, OrderedDict
24 from orddicts import DefaultOrderedDict
25 
26 locationKeyList = [
27  ("latitude", "float"),
28  ("longitude", "float"),
29  ("datetime", "string")
30 ]
31 
32 obsvars = {
33  'snow_depth': 'snowDepth',
34 }
35 
36 AttrData = {
37  'converter': os.path.basename(__file__),
38 }
39 
40 os.environ["TZ"] = "UTC"
41 
42 
43 class AFWA(object):
44 
45  def __init__(self, filename, mask, writer):
46  self.filenamefilename = filename
47  self.maskmask = mask
48  self.writerwriter = writer
49  self.varDictvarDict = defaultdict(lambda: defaultdict(dict))
50  self.outdataoutdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
51  self.loc_mdataloc_mdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
52  self.var_mdatavar_mdata = defaultdict(lambda: DefaultOrderedDict(OrderedDict))
53  self.unitsunits = {}
54  self._read_read()
55 
56  def _read(self):
57  # set up variable names for IODA
58  for iodavar in ['snowDepth']:
59  self.varDictvarDict[iodavar]['valKey'] = iodavar, self.writerwriter.OvalName()
60  self.varDictvarDict[iodavar]['errKey'] = iodavar, self.writerwriter.OerrName()
61  self.varDictvarDict[iodavar]['qcKey'] = iodavar, self.writerwriter.OqcName()
62  self.unitsunits[iodavar] = 'm'
63  data = pygrib.open(self.filenamefilename)
64  lat, lon = data[1].latlons()
65  lons = lon[:].ravel()
66  lats = lat[:].ravel()
67  vals = data[1].values[:].ravel()
68 
69  # use stereographic projection to calculate lat/lon as read of
70  # lat/lon is not correct for afwa grib1 file
71  lat1 = data[1]['latitudeOfFirstGridPointInDegrees']
72  lon1 = data[1]['longitudeOfFirstGridPointInDegrees']
73  nx = data[1]['Nx']
74  ny = data[1]['Ny']
75  dx = data[1]['DxInMetres']
76  dy = data[1]['DyInMetres']
77  # this works for corner assumption to get symmetric data
78  dxfac = 1.000376522
79  dx = dxfac*dx
80  dy = dxfac*dy
81 
82  myparams = data[1].projparams
83  # reset Lat of True Origin(lat_ts)for Soutern Hemisphere grib file
84  if myparams['lat_0'] == -90.0:
85  myparams['lat_ts'] = -60.0
86 
87  pj = pyproj.Proj(myparams)
88  llcrnrx, llcrnry = pj(lon1, lat1)
89  x = llcrnrx - dx*np.arange(nx)
90  y = llcrnry + dy*np.arange(ny)
91  x, y = np.meshgrid(x, y)
92  lon, lat = pj(x, y, inverse=True)
93  lons = lon[:].ravel()
94  lats = lat[:].ravel()
95  # setup snowCover and mask_flag
96  errs = 0.0*vals.astype('float32')
97  qflg = 0*vals.astype('int32')
98  times = np.empty_like(vals, dtype=object)
99 
100  if self.maskmask == "maskout":
101  mask = np.logical_not(vals.mask)
102  vals = vals[mask]
103  errs = errs[mask]
104  qflg = qflg[mask]
105  lons = lons[mask]
106  lats = lats[mask]
107  times = times[mask]
108  # get global attributes
109  start_datetime = data[1].analDate
110  base_datetime = start_datetime.isoformat() + "Z"
111  data.close()
112  AttrData['date_time_string'] = base_datetime
113 
114  for i in range(len(lons)):
115  times[i] = base_datetime
116  self.loc_mdataloc_mdata['datetime'] = self.writerwriter.FillNcVector(times, "datetime")
117  self.loc_mdataloc_mdata['latitude'] = lats
118  self.loc_mdataloc_mdata['longitude'] = lons
119  for iodavar in ['snowDepth']:
120  self.outdataoutdata[self.varDictvarDict[iodavar]['valKey']] = vals
121  self.outdataoutdata[self.varDictvarDict[iodavar]['errKey']] = errs
122  self.outdataoutdata[self.varDictvarDict[iodavar]['qcKey']] = qflg
123  self.writerwriter._nvars = len(obsvars)
124  self.writerwriter._nlocs = len(self.loc_mdataloc_mdata['datetime'])
125 
126 
127 def main():
128 
129  parser = argparse.ArgumentParser(
130  description=('Read AFWA snow depth file(s) and Converter'
131  ' of native grib format for observations of snow'
132  ' depth to IODA netCDF format.')
133  )
134  parser.add_argument('-i', '--input',
135  help="name of afwa snow depth input file(s)",
136  type=str, required=True)
137  parser.add_argument('-o', '--output',
138  help="name of ioda output file",
139  type=str, required=True)
140  optional = parser.add_argument_group(title='optional arguments')
141  optional.add_argument(
142  '-m', '--mask',
143  help="maskout missing values: maskout/default, default=none",
144  type=str, required=True)
145 
146  args = parser.parse_args()
147 
148  writer = iconv.NcWriter(args.output, locationKeyList)
149 
150  # Read in the profiles
151  snod = AFWA(args.input, args.mask, writer)
152 
153  writer.BuildNetcdf(snod.outdata, snod.loc_mdata, snod.var_mdata, AttrData, snod.units)
154 
155 
156 if __name__ == '__main__':
157  main()
def __init__(self, filename, mask, writer)