Fixed issue with finding wrwp_configuration
[baltrad-wrwp.git] / pywrwp / baltrad_wrwp_pgf_plugin.py
1 '''
2 Copyright (C) 2010- Swedish Meteorological and Hydrological Institute (SMHI)
3
4 This file is part of baltrad-wrwp.
5
6 baltrad-wrwp is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 baltrad-wrwp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 See the GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with baltrad-wrwp.  If not, see <http://www.gnu.org/licenses/>.
18
19 '''
20 ## Plugin for generating the wrwp product generation that is initiated from the beast
21 ## framework.
22 ## Register in pgf with
23 ## --name=eu.baltrad.beast.generatewrwp
24 ## --floats=minelevationangle,velocitythreshold --ints=interval,maxheight,mindistance,maxdistance --strings=fields -m baltrad_wrwp_pgf_plugin -f generate
25 ##
26 ## The WRWP generation is executed by providing a polar volume that a wind profile is calculated on
27 ##
28 ## wrwp = _wrwp.new()
29 ## wrwp.interval = ...
30 ## wrwp.maxheight = ...
31 ## ...
32 ## result = wrwp.generate(pvol)
33 ## 
34 ## @file
35 ## @author Anders Henja, SMHI
36 ## @date 2013-09-25
37 ##
38 ## @co-autor Ulf E. Nordh, SMHI
39 ## @date 2018-02-08
40 ##
41 ## Slight adjustment done to the call to wrwp.generate in function generate.
42 ## The call is done with try-except to deal with the situation that
43 ## the returned result is NULL. In addition, logging is inserted.
44 ##
45 ## @co-autor Ulf E. Nordh, SMHI
46 ## @date 2019-10-08
47 ##
48 ## From additional requirements, some of the parameters given in wrwp.h is given in a configuration
49 ## file instead for greater ease of adjustments without having to re-compile the code. The selected format
50 ## is xml.
51
52 import _wrwp
53 import _rave
54 import _raveio
55 import _polarvolume
56 import string
57 import logging
58 import rave_pgf_logger
59 import rave_tempfile
60 import odim_source
61 import math
62 import os
63 import sys
64 import fnmatch
65 import xml.etree.cElementTree as ET    
66 from rave_defines import CENTER_ID, GAIN, OFFSET
67
68 logger = rave_pgf_logger.create_logger()
69
70 # Configuration file probably is located in ../../../config/wrwp_config.xml relative to where this program is placed.
71 WRWP_CONFIG_FILE=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))),"config/wrwp_config.xml")
72
73 ravebdb = None
74 try:
75   import rave_bdb
76   ravebdb = rave_bdb.rave_bdb()
77 except:
78   logger.exception("Failed to initialize rave db")
79
80 # Locates a file (pattern) in a directory tree (path)
81 def find(pattern, path):
82   result = []
83   for root, dirs, files in os.walk(path):
84     for name in files:
85       if fnmatch.fnmatch(name, pattern):
86         result.append(os.path.join(root, name))
87   return result
88
89 ## Creates a dictionary from a rave argument list
90 #@param arglist the argument list
91 #@return a dictionary
92 def arglist2dict(arglist):
93   result={}
94   for i in range(0, len(arglist), 2):
95     result[arglist[i]] = arglist[i+1]
96   return result
97
98 ##
99 # Converts a string into a number, either int or float
100 # @param sval the string to translate
101 # @return the translated value
102 # @throws ValueError if value not could be translated
103 #
104     
105 def strToNumber(sval):
106   if type(sval) is not str: # Avoid doing anything if it is not a string as input
107     return sval
108   else:
109     try:
110       return int(sval)
111     except ValueError:
112       return float(sval)
113
114 ## Creates a vertical profile
115 #@param files the list of files to be used for generating the vertical profile
116 #@param arguments the arguments defining the vertical profile
117 #@return a temporary h5 file with the vertical profile
118 def generate(files, arguments):
119   args = arglist2dict(arguments)
120   wrwp = _wrwp.new()
121   fields = None
122
123   # Since the config can be placed in a few different places, we first check current directory, then the environment variable WRWP_CONFIG_FILE, if it doesn't exist or point to non-existing
124   # config file. We try a file located relative to this script (WRWP_CONFIG_FILE) defined above. Finally we try anything under /etc/baltrad.
125   # Hopefully, one of those places will be enough.
126   path2config = None
127   if os.path.exists("wrwp_config.xml"):
128     path2config = "wrwp_config.xml"
129     
130   if path2config is None and "WRWP_CONFIG_FILE" in os.environ:
131     if os.path.exists(os.environ["WRWP_CONFIG_FILE"]):
132       path2config=os.environ["WRWP_CONFIG_FILE"]
133   
134   if path2config is None and os.path.exists(WRWP_CONFIG_FILE):
135     path2config = WRWP_CONFIG_FILE
136   
137   if path2config is None:
138     etcpaths = find('wrwp_config.xml', '/etc/baltrad')
139     if len(etcpaths) > 0:
140       path2config=etcpaths[0]
141       
142   if path2config is None or not os.path.exists(path2config):      
143     logger.info("Could not find any wrwp_config.xml file in any of the expected locations")
144     return None
145
146   root = ET.parse(str(path2config)).getroot()
147
148   for param in root.findall('param'):
149     if param.get('name') == 'EMAX':
150       wrwp.emax = strToNumber(param.find('value').text)
151     if param.get('name') == 'EMIN':
152       wrwp.emin = strToNumber(param.find('value').text)
153     if param.get('name') == 'DMAX':
154       wrwp.dmax = strToNumber(param.find('value').text)
155     if param.get('name') == 'DMIN':
156       wrwp.dmin = strToNumber(param.find('value').text)
157     if param.get('name') == 'VMIN':
158       wrwp.vmin = strToNumber(param.find('value').text)
159     if param.get('name') == 'NMIN_WND':
160       wrwp.nmin_wnd = strToNumber(param.find('value').text)
161     if param.get('name') == 'NMIN_REF':
162       wrwp.nmin_ref = strToNumber(param.find('value').text)
163     if param.get('name') == 'FF-MAX':
164       wrwp.ff_max = strToNumber(param.find('value').text)
165     if param.get('name') == 'DZ':
166       wrwp.dz = strToNumber(param.find('value').text)
167     if param.get('name') == 'HMAX':
168       wrwp.hmax = strToNumber(param.find('value').text)
169     if param.get('name') == 'NODATA_VP':
170       wrwp.nodata_VP = strToNumber(param.find('value').text)
171     if param.get('name') == 'UNDETECT_VP':
172       wrwp.undetect_VP = strToNumber(param.find('value').text)
173     if param.get('name') == 'GAIN_VP':
174       wrwp.gain_VP = strToNumber(param.find('value').text)
175     if param.get('name') == 'OFFSET_VP':
176       wrwp.offset_VP = strToNumber(param.find('value').text)
177
178     if param.get('name') == 'QUANTITIES':
179       QUANTITIES = param.find('value').text
180
181     # Parameter values from the web-GUI, they overwright the ones above.
182     if "interval" in args.keys():
183       wrwp.dz = strToNumber(args["interval"])
184     if "maxheight" in args.keys():
185       wrwp.hmax = strToNumber(args["maxheight"])
186     if "mindistance" in args.keys():
187       wrwp.dmin = strToNumber(args["mindistance"])
188     if "maxdistance" in args.keys():
189       wrwp.dmax = strToNumber(args["maxdistance"]) 
190     if "minelevationangle" in args.keys():
191       wrwp.emin = strToNumber(args["minelevationangle"])
192     if "maxelevationangle" in args.keys():
193       wrwp.emax = strToNumber(args["maxelevationangle"])
194     if "velocitythreshold" in args.keys():
195       wrwp.vmin = strToNumber(args["velocitythreshold"])
196     if "maxvelocitythreshold" in args.keys():
197       wrwp.ff_max = strToNumber(args["maxvelocitythreshold"])
198     if "minsamplesizereflectivity" in args.keys():
199       wrwp.nmin_ref = strToNumber(args["minsamplesizereflectivity"])
200     if "minsamplesizewind" in args.keys():
201       wrwp.nmin_wnd = strToNumber(args["minsamplesizewind"])
202     if "fields" in args.keys():
203       fields = args["fields"]
204
205     if fields == None:
206       fields = QUANTITIES # If no fields are given in the web-GUI, we build wrwp with all the supported quantities
207     
208   if len(files) != 1:
209     raise AttributeError("Must call plugin with _one_ polar volume")
210   
211   logger.debug("Start generating vertical profile from polar volume %s"%files[0])
212
213   obj = None
214   if ravebdb != None:
215     obj = ravebdb.get_rave_object(files[0])
216   else:
217     rio = _raveio.open(files[0])
218     obj = rio.object
219
220   if not _polarvolume.isPolarVolume(obj):
221     raise AttributeError("Must call plugin with a polar volume")
222
223   try:
224     profile = wrwp.generate(obj, fields)  
225     fileno, outfile = rave_tempfile.mktemp(suffix='.h5', close="True")
226     ios = _raveio.new()
227     ios.object = profile
228     ios.filename = outfile
229     ios.save()
230     logger.debug("Finished generating vertical profile from polar volume %s"%files[0])
231     return outfile
232   except:
233     logger.info("No vertical profile could be generated from polar volume %s"%files[0])
234     return None