IODA Bundle
metar_csv2ioda.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
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 # author: Greg Thompson gthompsn AT ucar DOT edu
10 # This script will work with decoded, CSV files created by gthompsn stored at NCAR
11 # data-access.ucar.edu:/glade/campaign/ral/aap/gthompsn/METARs/2019/20191231/2019123118_metars.csv.gz
12 #
13 
14 import sys
15 import os
16 import math
17 from datetime import datetime
18 from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
19 from pathlib import Path
20 import csv
21 import netCDF4
22 
23 IODA_CONV_PATH = Path(__file__).parent/"@SCRIPT_LIB_PATH@"
24 if not IODA_CONV_PATH.is_dir():
25  IODA_CONV_PATH = Path(__file__).parent/'..'/'lib-python'
26 sys.path.append(str(IODA_CONV_PATH.resolve()))
27 
28 import meteo_utils
29 import ioda_conv_ncio as iconv
30 from orddicts import DefaultOrderedDict
31 
32 os.environ["TZ"] = "UTC"
33 
34 
35 class reformatMetar(object):
36 
37  def __init__(self, filename, date):
38 
39  self.filenamefilename = filename
40  self.datedate = date
42  self.float_fillfloat_fill = netCDF4.default_fillvals['f4']
43 
44  # Read in CSV-formatted file of METAR data
45  self._rd_metars_rd_metars()
46 
47  return
48 
49  def _rd_metars(self):
50 
51  # data is the dictionary of incoming observation (METAR) data
52  data = {}
53 
54  data['ob_icao'] = []
55  data['ob_lat'] = []
56  data['ob_lon'] = []
57  data['ob_time'] = []
58  data['ob_datetime'] = []
59  data['ob_elev'] = []
60  data['ob_hght'] = []
61  data['ob_psfc'] = []
62  data['ob_temp'] = []
63  data['ob_spfh'] = []
64  data['ob_uwnd'] = []
65  data['ob_vwnd'] = []
66 
67  '''
68  Read in the METARs data
69  Header contains: Unix_time,DateString,ICAO,Latitude,Longitude,Elev,Temp,Dewp,Wdir,Wspd,Wgst,Vis,\
70  Pcp,Pcp3h,Pcp6h,Pcp24h,QcFlag,WxString,WxCode,Altimeter,Cvg1,Bas1,Cvg2,Bas2,Cvg3,Bas3,Length,Raw
71  '''
72 
73  # open file in read mode
74  with open(self.filenamefilename, 'r') as fh:
75  # pass the file object to reader() to get the reader object
76  csv_dict_reader = csv.DictReader(fh)
77  column_names = csv_dict_reader.fieldnames
78  print(column_names)
79  # Iterate over each row in the csv using reader object
80  for row in csv_dict_reader:
81  # row variable is a list that represents a row in csv
82 
83  if row['ICAO'] is '':
84  continue
85  else:
86  icao = str(row['ICAO'])
87  try:
88  utime = int(row['Unix_time'])
89  lat = float(row['Latitude'])
90  lon = float(row['Longitude'])
91  elev = float(row['Elev'])
92  if (elev < -999 or elev > 8450):
93  elev = self.float_fillfloat_fill
94  hght = self.float_fillfloat_fill
95  else:
96  hght = elev + 2.0 # Height of observation assumed 2 meters above station elevation
97  except (csv.Error, ValueError):
98  continue
99  try:
100  temp = float(row['Temp']) + self.meteo_utilsmeteo_utils.C_2_K
101  except (csv.Error, ValueError):
102  temp = self.float_fillfloat_fill
103  try:
104  dewp = float(row['Dewp']) + self.meteo_utilsmeteo_utils.C_2_K
105  except (csv.Error, ValueError):
106  dewp = self.float_fillfloat_fill
107  try:
108  wdir = float(row['Wdir'])
109  except (csv.Error, ValueError):
110  wdir = self.float_fillfloat_fill
111  try:
112  wspd = float(row['Wspd']) * self.meteo_utilsmeteo_utils.KTS_2_MS
113  except (csv.Error, ValueError):
114  wspd = self.float_fillfloat_fill
115 
116  if ((wdir is not self.float_fillfloat_fill) and (wspd is not self.float_fillfloat_fill)):
117  if (wdir == 0 and wspd == 0):
118  uwnd = 0.0
119  vwnd = 0.0
120  elif (wdir > 0 and wdir <= 360 and wspd > 0):
121  uwnd, vwnd = self.meteo_utilsmeteo_utils.dir_speed_2_uv(wdir, wspd)
122  else:
123  uwnd = self.float_fillfloat_fill
124  vwnd = self.float_fillfloat_fill
125  else:
126  uwnd = self.float_fillfloat_fill
127  vwnd = self.float_fillfloat_fill
128 
129  try:
130  altim = float(row['Altimeter'])
131  psfc = self.meteo_utilsmeteo_utils.altim_2_sfcPressure(altim, elev)
132  except (csv.Error, ValueError):
133  altim = self.float_fillfloat_fill
134  psfc = self.float_fillfloat_fill
135 
136  if ((psfc is not self.float_fillfloat_fill) and (temp is not self.float_fillfloat_fill) and (dewp is not self.float_fillfloat_fill)):
137  spfh = self.meteo_utilsmeteo_utils.specific_humidity(dewp, psfc)
138  else:
139  spfh = self.float_fillfloat_fill
140 
141  data['ob_icao'].append(icao)
142  data['ob_time'].append(utime)
143  data['ob_datetime'].append(datetime.fromtimestamp(utime).strftime("%Y-%m-%dT%H:%M:%SZ"))
144  data['ob_lat'].append(lat)
145  data['ob_lon'].append(lon)
146  data['ob_elev'].append(elev)
147  data['ob_hght'].append(hght)
148  data['ob_psfc'].append(psfc)
149  data['ob_temp'].append(temp)
150  data['ob_spfh'].append(spfh)
151  data['ob_uwnd'].append(uwnd)
152  data['ob_vwnd'].append(vwnd)
153 
154  fh.close()
155  self.datadata = data
156 
157  return
158 
159 
160 class IODA(object):
161 
162  def __init__(self, filename, date, varDict, obsList):
163  '''
164  Initialize IODA writer class,
165  transform to IODA data structure and,
166  write out to IODA file.
167  '''
168 
169  self.filenamefilename = filename
170  self.datedate = date
171  self.varDictvarDict = varDict
172 
173  self.locKeyListlocKeyList = [
174  ("station_id", "string"),
175  ("latitude", "float"),
176  ("longitude", "float"),
177  ("station_elevation", "float"),
178  ("height", "float"),
179  ("datetime", "string")
180  ]
181 
182  self.AttrDataAttrData = {
183  'odb_version': 1,
184  'date_time_string': self.datedate.strftime("%Y-%m-%dT%H:%M:%SZ")
185  }
186 
187  self.writerwriter = iconv.NcWriter(self.filenamefilename, self.locKeyListlocKeyList)
188 
189  self.keyDictkeyDict = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
190  for key in self.varDictvarDict.keys():
191  value = self.varDictvarDict[key]
192  self.keyDictkeyDict[key]['valKey'] = value, self.writerwriter.OvalName()
193  self.keyDictkeyDict[key]['errKey'] = value, self.writerwriter.OerrName()
194  self.keyDictkeyDict[key]['qcKey'] = value, self.writerwriter.OqcName()
195 
196  # data is the dictionary containing IODA friendly data structure
197  self.datadata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
198 
199  recKey = 0
200 
201  for obs in obsList:
202 
203  for n in range(len(obs.data['ob_lat'])):
204 
205  icao = obs.data['ob_icao'][n]
206  lat = obs.data['ob_lat'][n]
207  lon = obs.data['ob_lon'][n]
208  elev = obs.data['ob_elev'][n]
209  hght = obs.data['ob_hght'][n]
210  dtg = obs.data['ob_datetime'][n]
211  locKey = icao, lat, lon, elev, hght, dtg
212 
213  # print ("obs iterate: " + str(n) + ", " + icao + ", " + str(lat) + ", " + str(lon) + ", " + str(elev) + ", " + dtg)
214 
215  for key in self.varDictvarDict.keys():
216 
217  val = obs.data[key][n]
218  err = 0.0
219  qc = 2
220 
221  valKey = self.keyDictkeyDict[key]['valKey']
222  errKey = self.keyDictkeyDict[key]['errKey']
223  qcKey = self.keyDictkeyDict[key]['qcKey']
224 
225  self.datadata[recKey][locKey][valKey] = val
226  self.datadata[recKey][locKey][errKey] = err
227  self.datadata[recKey][locKey][qcKey] = qc
228 
229  recKey += 1
230 
231  (ObsVars, LocMdata, VarMdata) = self.writerwriter.ExtractObsData(self.datadata)
232  self.writerwriter.BuildNetcdf(ObsVars, LocMdata, VarMdata, self.AttrDataAttrData)
233 
234  return
235 
236 
237 def main():
238 
239  desc = 'Convert CSV-formatted METAR data to IODA netCDF4 format'
240  parser = ArgumentParser(
241  description=desc,
242  formatter_class=ArgumentDefaultsHelpFormatter)
243  parser.add_argument(
244  '-i', '--input', help='name of the input METARs CSV-formatted file',
245  type=str, nargs='+', required=True)
246  parser.add_argument(
247  '-o', '--output', help='name of the output netCDF IODA-ready file',
248  type=str, required=True, default=None)
249  parser.add_argument(
250  '-d', '--date', help='file date', metavar='YYYYMMDDHH',
251  type=str, required=True)
252 
253  args = parser.parse_args()
254 
255  fList = args.input
256  foutput = args.output
257  fdate = datetime.strptime(args.date, '%Y%m%d%H')
258 
259  obsList = []
260  for fname in fList:
261  obs = reformatMetar(fname, fdate)
262  obsList.append(obs)
263 
264  varDict = {
265  'ob_temp': 'air_temperature',
266  'ob_spfh': 'specific_humidity',
267  'ob_psfc': 'surface_pressure',
268  'ob_uwnd': 'eastward_wind',
269  'ob_vwnd': 'northward_wind'
270  }
271 
272  IODA(foutput, fdate, varDict, obsList)
273 
274 
275 if __name__ == '__main__':
276  main()
def __init__(self, filename, date, varDict, obsList)
def __init__(self, filename, date)