1 | # Python script to transfomr ASCII Time-series WRF outputs to netCDF |
---|
2 | # From L. Fita work in different places: LMD (France) |
---|
3 | # More information at: http://www.xn--llusfb-5va.cat/python/PyNCplot |
---|
4 | # |
---|
5 | # pyNCplot and its component TS_ASCII_netCDF.py comes with ABSOLUTELY NO WARRANTY. |
---|
6 | # This work is licendes under a Creative Commons |
---|
7 | # Attribution-ShareAlike 4.0 International License (http://creativecommons.org/licenses/by-sa/4.0) |
---|
8 | # |
---|
9 | |
---|
10 | ## g.e. # TS_ASCII_netCDF.py -f //media/ExtDiskD/bkup/ciclad/etudes/WL_HyMeX/iop15/wrf/run/control/stations_20121018000000-20121022000000/h0001.d01.TS -s 20121018000000 |
---|
11 | |
---|
12 | import os |
---|
13 | from optparse import OptionParser |
---|
14 | import numpy as np |
---|
15 | from netCDF4 import Dataset as NetCDFFile |
---|
16 | import re |
---|
17 | |
---|
18 | main = 'TS_ASCII_netCDF.py' |
---|
19 | errormsg='ERROR -- error -- ERROR -- error' |
---|
20 | warnmsg='WARNING -- warning -- WARNING -- warning' |
---|
21 | |
---|
22 | fillValue = 1.e20 |
---|
23 | |
---|
24 | def searchInlist(listname, nameFind): |
---|
25 | """ Function to search a value within a list |
---|
26 | listname = list |
---|
27 | nameFind = value to find |
---|
28 | >>> searInlist(['1', '2', '3', '5'], '5') |
---|
29 | True |
---|
30 | """ |
---|
31 | for x in listname: |
---|
32 | if x == nameFind: |
---|
33 | return True |
---|
34 | return False |
---|
35 | |
---|
36 | def reduce_spaces(string): |
---|
37 | """ Function to give words of a line of text removing any extra space |
---|
38 | """ |
---|
39 | values = string.replace('\n','').split(' ') |
---|
40 | vals = [] |
---|
41 | for val in values: |
---|
42 | if len(val) > 0: |
---|
43 | vals.append(val) |
---|
44 | |
---|
45 | return vals |
---|
46 | |
---|
47 | def reduce_last_spaces(string): |
---|
48 | """ Function to reduce the last right spaces from a string |
---|
49 | string= string to remove the spaces at the righ side of the string |
---|
50 | >>> reduce_last_spaces('Hola Lluis ') |
---|
51 | 'Hola Lluis' |
---|
52 | """ |
---|
53 | fname = 'reduce_last_spaces' |
---|
54 | |
---|
55 | Lstring = len(string) |
---|
56 | |
---|
57 | firstspace = -1 |
---|
58 | for istr in range(Lstring-1,0,-1): |
---|
59 | if string[istr:istr+1] == ' ': |
---|
60 | firstspace = istr |
---|
61 | else: |
---|
62 | break |
---|
63 | |
---|
64 | if firstspace == -1: |
---|
65 | print errormsg |
---|
66 | print ' ' + fname + ": string '" + string + "' is not ended by spaces!!" |
---|
67 | print ' char. number of last character:',ord(string[Lstring:Lstring+1]) |
---|
68 | quit(-1) |
---|
69 | |
---|
70 | newstr = string[0:firstspace] |
---|
71 | |
---|
72 | return newstr |
---|
73 | |
---|
74 | def set_attribute(ncv, attrname, attrvalue): |
---|
75 | """ Sets a value of an attribute of a netCDF variable. Removes previous attribute value if exists |
---|
76 | ncv = object netcdf variable |
---|
77 | attrname = name of the attribute |
---|
78 | attrvalue = value of the attribute |
---|
79 | """ |
---|
80 | |
---|
81 | attvar = ncv.ncattrs() |
---|
82 | if searchInlist(attvar, attrname): |
---|
83 | attr = ncv.delncattr(attrname) |
---|
84 | |
---|
85 | attr = ncv.setncattr(attrname, attrvalue) |
---|
86 | |
---|
87 | return attr |
---|
88 | |
---|
89 | def rmNOnum(string): |
---|
90 | """ Removing from a string all that characters which are not numbers |
---|
91 | # From: http://stackoverflow.com/questions/4289331/python-extract-numbers-from-a-string |
---|
92 | """ |
---|
93 | fname = 'rmNOnum' |
---|
94 | |
---|
95 | newstr = str(re.findall("[-+]?\d+[\.]?\d*", string)[0]) |
---|
96 | |
---|
97 | return newstr |
---|
98 | |
---|
99 | def basicvardef(varobj, vstname, vlname, vunits): |
---|
100 | """ Function to give the basic attributes to a variable |
---|
101 | varobj= netCDF variable object |
---|
102 | vstname= standard name of the variable |
---|
103 | vlname= long name of the variable |
---|
104 | vunits= units of the variable |
---|
105 | """ |
---|
106 | attr = varobj.setncattr('standard_name', vstname) |
---|
107 | attr = varobj.setncattr('long_name', vlname) |
---|
108 | attr = varobj.setncattr('units', vunits) |
---|
109 | |
---|
110 | return |
---|
111 | |
---|
112 | def values_fortran_fmt(lin,fFMT): |
---|
113 | """ Function to give back the values of an ASCII line according to its fortran printing format |
---|
114 | lin= ASCII line |
---|
115 | fFMT= list with the fortran FORMAT formats |
---|
116 | forline = 'Natchitoches (RGNL) 1 11 0011 ( 31.733, -93.100) ( 28, 157) ( 31.761, -93.113) 41.2 meters' |
---|
117 | formats = ['A26', 'I2', 'I3', 'A6', 'A2', 'F7.3', 'A1', 'F8.3', 'A3', 'I4', 'A1', 'I4', |
---|
118 | 'A3', 'F7.3', 'A1', 'F8.3', 'A2', 'F6.1', 'A7'] |
---|
119 | >>> values_fortran_fmt(forline, formats) |
---|
120 | ['Natchitoches (RGNL) ', 1, 11, ' 0011 ', ' ( ', 31.733, ', ', -93.1, ') ( ', 28, ', ', 157, ') |
---|
121 | ( ', 31.761, ', ', -93.113, ') ', 41.2, ' meters'] |
---|
122 | """ |
---|
123 | fname = 'values_fortran_fmt' |
---|
124 | |
---|
125 | afmts = ['A', 'D', 'F', 'I'] |
---|
126 | |
---|
127 | if lin == 'h': |
---|
128 | print fname + '_____________________________________________________________' |
---|
129 | print values_fortran_fmt.__doc__ |
---|
130 | quit() |
---|
131 | |
---|
132 | fvalues = [] |
---|
133 | ichar=0 |
---|
134 | ival = 0 |
---|
135 | for ft in fFMT: |
---|
136 | Lft = len(ft) |
---|
137 | if ft[0:1] == 'A' or ft[0:1] == 'a': |
---|
138 | Lfmt = int(ft[1:Lft+1]) |
---|
139 | fvalues.append(lin[ichar:ichar+Lfmt+1]) |
---|
140 | ichar = ichar + Lfmt |
---|
141 | elif ft[0:1] == 'F' or ft[0:1] == 'f': |
---|
142 | if ft.find('.') != -1: |
---|
143 | Lft = len(ft.split('.')[0]) |
---|
144 | Lfmt = int(ft[1:Lft]) |
---|
145 | fvalues.append(np.float(rmNOnum(lin[ichar:ichar+Lfmt+1]))) |
---|
146 | ichar = ichar + Lfmt |
---|
147 | elif ft[0:1] == 'D' or ft[0:1] == 'd': |
---|
148 | if ft.find('.') != -1: |
---|
149 | Lft = len(ft.split('.')[0]) |
---|
150 | Lfmt = int(ft[1:Lft]) |
---|
151 | fvalues.append(np.float64(rmNOnum(lin[ichar:ichar+Lfmt+1]))) |
---|
152 | ichar = ichar + Lfmt |
---|
153 | elif ft[0:1] == 'I' or ft[0:1] == 'i': |
---|
154 | Lfmt = int(ft[1:Lft+1]) |
---|
155 | fvalues.append(int(rmNOnum(lin[ichar:ichar+Lfmt+1]))) |
---|
156 | ichar = ichar + Lfmt |
---|
157 | elif ft.find('X') != -1 or ft.find('x') != -1: |
---|
158 | print ' ' + fname + ': skipping space' |
---|
159 | ichar = ichar + int(rmNOnum(ft)) |
---|
160 | else: |
---|
161 | print errormsg |
---|
162 | print ' ' + fname + ": format '" + ft[0:1] + "' not ready!!" |
---|
163 | print ' Available formats:',afmts |
---|
164 | quit(-1) |
---|
165 | |
---|
166 | ival = ival + 1 |
---|
167 | |
---|
168 | return fvalues |
---|
169 | |
---|
170 | |
---|
171 | def ts_header(ln): |
---|
172 | """ Function to get the values of the header of the *.TS files |
---|
173 | line=ASCII lines with the header of the TS file |
---|
174 | getting the line format from WRFV3.3 'EMCORE' in file 'share/wrf_timeseries.F' |
---|
175 | """ |
---|
176 | fname = 'ts_header' |
---|
177 | |
---|
178 | fmts=['A26', 'I2', 'I3', 'A6', 'A2', 'F7.3', 'A1', 'F8.3', 'A3', 'I4', 'A1', 'I4',\ |
---|
179 | 'A3', 'F7.3', 'A1', 'F8.3', 'A2', 'F6.1', 'A7'] |
---|
180 | |
---|
181 | headervalues = values_fortran_fmt(ln,fmts) |
---|
182 | |
---|
183 | return headervalues |
---|
184 | |
---|
185 | def variables_values(varName): |
---|
186 | """ Function to provide values to plot the different variables values from ASCII file |
---|
187 | 'variables_values.dat' |
---|
188 | variables_values(varName) |
---|
189 | [varName]= name of the variable |
---|
190 | return: [var name], [std name], [minimum], [maximum], |
---|
191 | [long name]('|' for spaces), [units], [color palette] (following: |
---|
192 | http://matplotlib.org/1.3.1/examples/color/colormaps_reference.html) |
---|
193 | [varn]: original name of the variable |
---|
194 | NOTE: It might be better doing it with an external ASII file. But then we |
---|
195 | got an extra dependency... |
---|
196 | >>> variables_values('WRFght') |
---|
197 | ['z', 'geopotential_height', 0.0, 80000.0, 'geopotential|height', 'm2s-2', 'rainbow'] |
---|
198 | """ |
---|
199 | import subprocess as sub |
---|
200 | |
---|
201 | fname='variables_values' |
---|
202 | |
---|
203 | if varName == 'h': |
---|
204 | print fname + '_____________________________________________________________' |
---|
205 | print variables_values.__doc__ |
---|
206 | quit() |
---|
207 | |
---|
208 | # This does not work.... |
---|
209 | # folderins = sub.Popen(["pwd"], stdout=sub.PIPE) |
---|
210 | # folder = list(folderins.communicate())[0].replace('\n','') |
---|
211 | # From http://stackoverflow.com/questions/4934806/how-can-i-find-scripts-directory-with-python |
---|
212 | folder = os.path.dirname(os.path.realpath(__file__)) |
---|
213 | |
---|
214 | infile = folder + '/variables_values.dat' |
---|
215 | |
---|
216 | if not os.path.isfile(infile): |
---|
217 | print errormsg |
---|
218 | print ' ' + fname + ": File '" + infile + "' does not exist !!" |
---|
219 | quit(-1) |
---|
220 | |
---|
221 | # Variable name might come with a statistical surname... |
---|
222 | stats=['min','max','mean','stdv', 'sum'] |
---|
223 | |
---|
224 | # Variables with a statistical section on their name... |
---|
225 | NOstatsvars = ['zmaxth', 'zmax_th', 'lmax_th', 'lmaxth'] |
---|
226 | |
---|
227 | ifst = False |
---|
228 | if not searchInlist(NOstatsvars, varName.lower()): |
---|
229 | for st in stats: |
---|
230 | if varName.find(st) > -1: |
---|
231 | print ' '+ fname + ": varibale '" + varName + "' with a " + \ |
---|
232 | "statistical surname: '",st,"' !!" |
---|
233 | Lst = len(st) |
---|
234 | LvarName = len(varName) |
---|
235 | varn = varName[0:LvarName - Lst] |
---|
236 | ifst = True |
---|
237 | break |
---|
238 | if not ifst: |
---|
239 | varn = varName |
---|
240 | |
---|
241 | ncf = open(infile, 'r') |
---|
242 | |
---|
243 | for line in ncf: |
---|
244 | if line[0:1] != '#': |
---|
245 | values = line.replace('\n','').split(',') |
---|
246 | if len(values) != 8: |
---|
247 | print errormsg |
---|
248 | print "problem in varibale:'", values[0], \ |
---|
249 | 'it should have 8 values and it has',len(values) |
---|
250 | quit(-1) |
---|
251 | |
---|
252 | if varn[0:6] == 'varDIM': |
---|
253 | # Variable from a dimension (all with 'varDIM' prefix) |
---|
254 | Lvarn = len(varn) |
---|
255 | varvals = [varn[6:Lvarn+1], varn[6:Lvarn+1], 0., 1., \ |
---|
256 | "variable|from|size|of|dimension|'" + varn[6:Lvarn+1] + "'", '1', \ |
---|
257 | 'rainbow'] |
---|
258 | else: |
---|
259 | varvals = [values[1].replace(' ',''), values[2].replace(' ',''), \ |
---|
260 | np.float(values[3]), np.float(values[4]),values[5].replace(' ',''),\ |
---|
261 | values[6].replace(' ',''), values[7].replace(' ','')] |
---|
262 | if values[0] == varn: |
---|
263 | ncf.close() |
---|
264 | return varvals |
---|
265 | break |
---|
266 | |
---|
267 | print errormsg |
---|
268 | print ' ' + fname + ": variable '" + varn + "' not defined !!!" |
---|
269 | ncf.close() |
---|
270 | quit(-1) |
---|
271 | |
---|
272 | return |
---|
273 | |
---|
274 | ####### ###### ##### #### ### ## # |
---|
275 | |
---|
276 | parser = OptionParser() |
---|
277 | parser.add_option("-f", "--TS_file", dest="lfile", |
---|
278 | help="Time Series ASCII text file to use", metavar="FILE") |
---|
279 | parser.add_option("-s", "--SimulationStartTime", dest="stime", |
---|
280 | help="Starting time of the simulation ([YYYY][MM][DD][HH][MI][SS] format)", metavar="DATE") |
---|
281 | |
---|
282 | (opts, args) = parser.parse_args() |
---|
283 | |
---|
284 | |
---|
285 | tsvn = ['t', 'q', 'u', 'v', 'psfc', 'glw', 'gsw', 'hfx', 'lh', 'tsk', 'tslb1', 'rainc', 'rainnc', 'clw'] |
---|
286 | |
---|
287 | tsvln = ['2 m Temperature', '2 m vapor mixing ratio', '10 m U wind (earth-relative)', '10 m V wind (earth-relative)', 'surface pressure', 'downward longwave radiation flux at the ground (downward is positive)', 'net shortwave radiation flux at the ground (downward is positive)', 'surface sensible heat flux (upward is positive)', 'surface latent heat flux (upward is positive)', 'skin temperature', 'top soil layer temperature', 'rainfall from a cumulus scheme', 'rainfall from an explicit scheme', 'total column-integrated water vapor and cloud variables'] |
---|
288 | |
---|
289 | tsvu = ['K', 'kg/kg', 'm/s', 'm/s', 'Pa', 'W/m2', 'W/m2', 'W/m2', 'W/m2', 'K', 'K', 'mm', 'mm', '1'] |
---|
290 | |
---|
291 | |
---|
292 | ####### ####### |
---|
293 | ## MAIN |
---|
294 | ####### |
---|
295 | |
---|
296 | ofile = 'ts.nc' |
---|
297 | Ntsvariables = len(tsvn) |
---|
298 | |
---|
299 | if not os.path.isfile(opts.lfile): |
---|
300 | print errormsg |
---|
301 | print ' ' + main + ': Time-Series ASCII text file "' + opts.lfile + \ |
---|
302 | '" does not exist !!' |
---|
303 | print errormsg |
---|
304 | quit() |
---|
305 | |
---|
306 | if opts.stime is None: |
---|
307 | print errormsg |
---|
308 | print ' ' + main + ': No initial date/time of the simulation is provided!' |
---|
309 | quit(-1) |
---|
310 | else: |
---|
311 | stime = opts.stime |
---|
312 | refdate = stime[0:4] + '-' + stime[4:6] + '-' + stime[6:8] + ' ' + stime[8:10] + \ |
---|
313 | ':' + stime[10:12] + ':' + stime[12:14] |
---|
314 | |
---|
315 | objlfile = open(opts.lfile, 'r') |
---|
316 | |
---|
317 | objofile = NetCDFFile(ofile, 'w') |
---|
318 | |
---|
319 | # Creation of dimensions |
---|
320 | ## |
---|
321 | objofile.createDimension('time',None) |
---|
322 | |
---|
323 | set_attribute(objofile, 'author', 'Lluis Fita Borrell') |
---|
324 | set_attribute(objofile, 'institution', 'Laboratoire Meteorologique Dynamique') |
---|
325 | set_attribute(objofile, 'university', 'University Pierre et Marie Curie') |
---|
326 | set_attribute(objofile, 'center', 'Centre national de la recherche scientifique') |
---|
327 | set_attribute(objofile, 'country', 'France') |
---|
328 | set_attribute(objofile, 'city', 'Paris') |
---|
329 | set_attribute(objofile, 'script', 'TS_ASCII_netCFD.py') |
---|
330 | set_attribute(objofile, 'version', '1.0') |
---|
331 | |
---|
332 | time_step = [] |
---|
333 | psfc = [] |
---|
334 | rainc = [] |
---|
335 | rainnc = [] |
---|
336 | drydens = [] |
---|
337 | |
---|
338 | tsvals = {} |
---|
339 | |
---|
340 | iline=0 |
---|
341 | itz = 0 |
---|
342 | for line in objlfile: |
---|
343 | values = reduce_spaces(line) |
---|
344 | # print iline, values[0], dimz, Searchdimz |
---|
345 | # Writting general information |
---|
346 | if iline == 0: |
---|
347 | newvar = objofile.createVariable('station','c') |
---|
348 | valueshead = ts_header(line) |
---|
349 | |
---|
350 | set_attribute(newvar, 'name', reduce_last_spaces(valueshead[0])) |
---|
351 | set_attribute(newvar, 'acronym',valueshead[3].replace(' ','')) |
---|
352 | |
---|
353 | set_attribute(newvar, 'real_lon', valueshead[5]) |
---|
354 | set_attribute(newvar, 'real_lat', valueshead[7]) |
---|
355 | |
---|
356 | set_attribute(newvar, 'x_grid_point', valueshead[9]) |
---|
357 | set_attribute(newvar, 'y_grid_point', valueshead[11]) |
---|
358 | |
---|
359 | set_attribute(newvar, 'model_lon', valueshead[13]) |
---|
360 | set_attribute(newvar, 'model_lat', valueshead[15]) |
---|
361 | set_attribute(newvar, 'model_height', valueshead[17]) |
---|
362 | simstarttime = refdate |
---|
363 | else: |
---|
364 | tsvals[itz] = values |
---|
365 | time_step.append(np.float(values[1])) |
---|
366 | itz = itz + 1 |
---|
367 | iline = iline + 1 |
---|
368 | |
---|
369 | dimt = len(time_step) |
---|
370 | |
---|
371 | print ' Found:',dimt,'time steps' |
---|
372 | objlfile.close() |
---|
373 | |
---|
374 | time_stepv = np.zeros((dimt), dtype=np.float) |
---|
375 | tsvaluesv = np.zeros( (dimt,Ntsvariables), dtype= np.float) |
---|
376 | |
---|
377 | pracc = np.zeros((dimt), dtype=np.float) |
---|
378 | |
---|
379 | itz = 0 |
---|
380 | for it in range(dimt): |
---|
381 | time_stepv[it] = np.float(time_step[it]) |
---|
382 | |
---|
383 | for iv in range(Ntsvariables): |
---|
384 | tsvaluesv[it,iv] = np.float(tsvals[itz][iv+5]) |
---|
385 | |
---|
386 | pracc[it] = np.float(tsvals[it][16]) + np.float(tsvals[it][17]) |
---|
387 | itz = itz + 1 |
---|
388 | |
---|
389 | # time |
---|
390 | newvar = objofile.createVariable('time','f8',('time')) |
---|
391 | newvar[:] = time_stepv*3600. |
---|
392 | newattr = basicvardef(newvar, 'time', 'time', 'seconds since ' + \ |
---|
393 | simstarttime.replace('_',' ')) |
---|
394 | set_attribute(newvar, 'calendar', 'standard') |
---|
395 | |
---|
396 | dt = time_stepv[1] - time_stepv[0] |
---|
397 | |
---|
398 | # time-series variables |
---|
399 | for iv in range(Ntsvariables): |
---|
400 | if tsvn[iv] == 't' or tsvn[iv] == 'u' or tsvn[iv] == 'v' or tsvn[iv] == 'q': |
---|
401 | varname, stdname, minvar, maxvar, longname, unitsvar, cbarvar = \ |
---|
402 | variables_values('TS' + tsvn[iv]) |
---|
403 | tsu = unitsvar |
---|
404 | else: |
---|
405 | varname, stdname, minvar, maxvar, longname, unitsvar, cbarvar = \ |
---|
406 | variables_values(tsvn[iv]) |
---|
407 | tsu = tsvu[iv] |
---|
408 | |
---|
409 | newvar = objofile.createVariable(varname, 'f4', ('time')) |
---|
410 | newvar[:] = tsvaluesv[:,iv] |
---|
411 | |
---|
412 | newattr = basicvardef(newvar, stdname, longname.replace('|',' '), tsu) |
---|
413 | newattr = set_attribute(newvar, 'wrfTSname', tsvn[iv]) |
---|
414 | newattr = set_attribute(newvar, 'wrfTSdesc', tsvln[iv]) |
---|
415 | |
---|
416 | # Extra vars |
---|
417 | |
---|
418 | # pr |
---|
419 | varvals = np.zeros((dimt), dtype=np.float) |
---|
420 | varvals[1:dimt] = pracc[1:dimt] - pracc[0:dimt-1] |
---|
421 | varname, stdname, minvar, maxvar, longname, unitsvar, cbarvar = \ |
---|
422 | variables_values('RAINTOT') |
---|
423 | |
---|
424 | newvar = objofile.createVariable(varname, 'f4', ('time')) |
---|
425 | newvar[:] = varvals / dt |
---|
426 | newattr = basicvardef(newvar, stdname, longname.replace('|',' '), unitsvar ) |
---|
427 | |
---|
428 | objofile.sync() |
---|
429 | objofile.close() |
---|
430 | |
---|
431 | print 'Successfull generation of Time-Series netCDF file "' + ofile + '" !!!!!' |
---|