├── LICENSE
├── README.md
├── examples
├── pyradarmet_beam_blockage-Test.ipynb
├── pyradarmet_beam_blockage.ipynb
└── pyradarmet_examples.ipynb
├── pyradarmet
├── BeamBlock.py
├── __init__.py
├── attenuation.py
├── conversion.py
├── doppler.py
├── geometry.py
├── system.py
├── variables.py
└── zdrcal.py
├── scripts
├── README
├── cal_zdr
├── listfile_raw.py
└── zdrcal_3panel
└── setup.py
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | This General Public License does not permit incorporating your program into
326 | proprietary programs. If your program is a subroutine library, you may
327 | consider it more useful to permit linking proprietary applications with the
328 | library. If this is what you want to do, use the GNU Lesser General
329 | Public License instead of this License.
330 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PyRadarMet
2 | ===============
3 | Python Fundamental Calculations in Radar Meteorology package notes
4 |
5 | Originally Created: 5 February 2014
6 |
7 | ## Author
8 | Nick Guy - nick.guy@uwyo.edu
9 |
10 | Special thanks to Timothy Lang and Kai Muehlbauer for the insights and contributions.
11 |
12 | ## Package Details
13 | attenuation.py – Routines to calculate coefficients useful in attenuation calculations
14 |
15 | conversion.py – Routines to convert linear to log reflectivity and vice versa
16 |
17 | doppler.py – Routines to calculate a number of fundamental Doppler radar characteristics
18 | including unambiguous range and velocity,
19 | “Doppler dilemma” equation, dual PRF Vmax,…
20 |
21 | geometry.py – Routines to calculate such characteristics as effective radius,
22 | half-power radius, ray height, sample volumes, range corrections,
23 | beam blockage fractions,…
24 |
25 | system.py – Routines to calculate such characteristics as wavelength, frequency,
26 | pulse length, radar constant, effective antenna area, thermal noise, …
27 |
28 | variables.py – Routines to calculate such characteristics as CDR, LDR, ZDR, ZDP,…
29 | This is really basic at the moment as I haven’t even attempted
30 | dual-pol calcs as of yet.
31 |
32 | zdrcal.py – Routines to calculate ZDR offset of a dual-polarimetric radar. This is called
33 | by an executable called cal_zdr.
34 |
35 | BeamBlock - A class that allows the calculation of the geometric beam blocking.
36 |
37 | ## News
38 | This module has be ported over to an [R package](http://cran.r-project.org/web/packages/radar/) by Jose Gama.
39 |
40 | PyRadarMet is undergoing a port into the [wradlib](http://wradlib.bitbucket.org/) python package.
41 |
42 | Some modules now exist in wradlib. Many fundamental calculations are planned to
43 | be moved the [CSU_RadarTools](https://github.com/CSU-Radarmet/CSU_RadarTools) python package.
44 |
45 | Special packages (e.g. BeamBlock, ZDR cal scripts) are currently only available in PyRadarMet.
46 |
47 | ## Installation
48 | There are two options for install. The setup script now downloads the data listed below
49 | automatically.
50 |
51 | To download and install the data within the package directory:
52 |
53 | ```python
54 | python setup.py install
55 | ```
56 |
57 | This is the most robust way, as the data is always a relative path stored within the python 'egg'.
58 |
59 | To download the data and install it to a user-specified location an environmental variable must be set.
60 | The variable is 'GTOPO_DATA' and can be set on unix systems (in a bash environment) as such:
61 | ```python
62 | export GTOPO_DATA=/some/direcoty/for/the/data
63 | ```
64 |
65 | It is recommended that this be done in a shell profile file like .bashrc or .bash_profile
66 | Then run the install command:
67 | ```python
68 | python setup.py install
69 | ```
70 |
71 | The install took about 150 seconds on a university connection when installed in an external directory.
72 |
73 | The install took about 250 seconds when installed as part of the package.
74 |
75 | ##Data
76 | The data folder holds Digital Elevation Model data used in the BeamBlock routine.
77 | The data is the GTOPO30, a 30-arc second (~ 1 km) elevation data set.
78 | Data available from the U.S. Geological Survey.
79 | Downloaded from the [long term archive](https://lta.cr.usgs.gov/),
80 |
81 | using the [EarthExplorer](http://earthexplorer.usgs.gov/) tool.
82 |
83 | ## Dependencies
84 |
85 | Developed on the Anaconda distribution (1.9.1 & Python 2.7.7), tested to:
86 | Anaconda 2.1.0 and Python 2.7.8
87 | [Anaconda](https://store.continuum.io/cshop/anaconda/)
88 |
89 | It uses a typical scientific python stack:
90 | [Numpy](http://www.scipy.org)
91 | [Scipy](http://www.scipy.org)
92 | [matplotlib](http://matplotlib.org)
93 |
94 | ## Notes
95 | This software was originally an attempt to help me get used to programming in Python environment.
96 | I have tested more since it was originally developed and have modified accordingly.
97 |
98 | Please feel free to contact me with questions or suggestions.
99 | Do note this is a side project and it may take me some time to respond.
100 |
101 | This is open-source software, with no warranties extended.
102 |
--------------------------------------------------------------------------------
/examples/pyradarmet_examples.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "name": "",
4 | "signature": "sha256:5fab1a369b613b733e6592756d91ff921297d7d8f821ea21acf6f6982541798a"
5 | },
6 | "nbformat": 3,
7 | "nbformat_minor": 0,
8 | "worksheets": [
9 | {
10 | "cells": [
11 | {
12 | "cell_type": "code",
13 | "collapsed": false,
14 | "input": [
15 | "# Load needed packages\n",
16 | "%matplotlib inline\n",
17 | "\n",
18 | "import pyradarmet as rmet\n",
19 | "import matplotlib.pyplot as plt\n",
20 | "import numpy as np"
21 | ],
22 | "language": "python",
23 | "metadata": {},
24 | "outputs": [],
25 | "prompt_number": 1
26 | },
27 | {
28 | "cell_type": "code",
29 | "collapsed": false,
30 | "input": [
31 | "# For demonstration purposes, we'll use the DoE ARM CSAPR radar to test the functions.\n",
32 | "# Documentation can be found at:\n",
33 | "# http://www.arm.gov/publications/tech_reports/handbooks/csapr_handbook.pdf?id=21\n",
34 | "# See page 9 for specific system characteristics\n",
35 | "\n",
36 | "csapr_frequency = 6.25E9 # Hz\n",
37 | "csapr_pulse_width_min = 200E-9 # seconds\n",
38 | "csapr_pulse_width_max = 2E-6 # seconds\n",
39 | "csapr_beam_width = 0.9 # degrees\n",
40 | "csapr_gain = 45.1 # dB\n",
41 | "csapr_power = 350000 # W\n",
42 | "csapr_prf_min = 200 # Hz\n",
43 | "csapr_prf_max = 2.7E3 # Hz\n",
44 | "\n",
45 | "\n",
46 | "# Let's make up a couple loss parameters\n",
47 | "csapr_antenna_loss = 0.5 # dB\n",
48 | "csapr_receiver_loss = 1.0 # dB"
49 | ],
50 | "language": "python",
51 | "metadata": {},
52 | "outputs": [],
53 | "prompt_number": 10
54 | },
55 | {
56 | "cell_type": "code",
57 | "collapsed": false,
58 | "input": [
59 | "## Let's show some examples of radar system calculations ##\n",
60 | "## system.py ##\n",
61 | "wavelength = rmet.system.wavelength(csapr_frequency) # m\n",
62 | "\n",
63 | "pulse_length_min = rmet.system.pulse_length(csapr_pulse_width_min) # m\n",
64 | "\n",
65 | "pulse_length_max = rmet.system.pulse_length(csapr_pulse_width_max) # m\n",
66 | "\n",
67 | "radar_const = rmet.system.radar_const(csapr_power, csapr_gain, csapr_pulse_width_max,\n",
68 | " wavelength, csapr_beam_width, csapr_beam_width,\n",
69 | " csapr_antenna_loss, csapr_receiver_loss) # unitless\n",
70 | "\n",
71 | "antenna_effective_area = rmet.system.ant_eff_area(csapr_gain, wavelength) # m^2\n",
72 | "\n",
73 | "# Assume a spherical target of 0.5 mm diameter\n",
74 | "targ_diam = 0.5E-3\n",
75 | "targ_area = np.pi * (targ_diam/2)**2\n",
76 | "\n",
77 | "# Target volume at 50 m range\n",
78 | "targ_range = 50.\n",
79 | "target_power = rmet.system.power_target(csapr_power, csapr_gain, targ_area, targ_range) # W\n",
80 | "\n",
81 | "# Assume that it is water (don't need to specify dielectric constant, otherwise add K argument)\n",
82 | "xsec_backscatter_sphere = rmet.system.xsec_bscatter_sphere(targ_diam, wavelength) # m^2\n",
83 | "\n",
84 | "size_parameter = rmet.system.size_param(targ_diam, wavelength) # unitless\n",
85 | "\n",
86 | "Pt_return = rmet.system.power_return_target(csapr_power, csapr_gain, wavelength,\n",
87 | " xsec_backscatter_sphere, targ_range) # W\n"
88 | ],
89 | "language": "python",
90 | "metadata": {},
91 | "outputs": [],
92 | "prompt_number": 3
93 | },
94 | {
95 | "cell_type": "code",
96 | "collapsed": false,
97 | "input": [
98 | " # Now print out the answers to check the performance:\n",
99 | "print \"CSAPR wavelength = %g cm\" % (wavelength * 100) # put into cm\n",
100 | "print \"CSAPR minimum pulse length = %g m\" % (pulse_length_min)\n",
101 | "print \"CSAPR maximum pulse length = %g m\" % (pulse_length_max)\n",
102 | "print \"CSAPR radar constant = %g\" % radar_const\n",
103 | "print r'CSAPR antenna effective area = %g m^2' % antenna_effective_area\n",
104 | "print \"CSAPR target power return = %g W\" % target_power\n",
105 | "print r\"CSAPR crossectional backscatter area =%g m^2\" % xsec_backscatter_sphere\n",
106 | "print \"Size parameter of example = %g \" % size_parameter\n",
107 | "print \"CSAPR power return for example = % g W\" % Pt_return"
108 | ],
109 | "language": "python",
110 | "metadata": {},
111 | "outputs": [
112 | {
113 | "output_type": "stream",
114 | "stream": "stdout",
115 | "text": [
116 | "CSAPR wavelength = 4.8 cm\n",
117 | "CSAPR minimum pulse length = 30 m\n",
118 | "CSAPR maximum pulse length = 300 m\n",
119 | "CSAPR radar constant = 1.45312e+15\n",
120 | "CSAPR antenna effective area = 5.93298 m^2\n",
121 | "CSAPR target power return = 0.0707861 W\n",
122 | "CSAPR crossectional backscatter area =7.79059e-13 m^2\n",
123 | "Size parameter of example = 0.0327249 \n",
124 | "CSAPR power return for example = 5.30409e-11 W\n"
125 | ]
126 | }
127 | ],
128 | "prompt_number": 4
129 | },
130 | {
131 | "cell_type": "code",
132 | "collapsed": false,
133 | "input": [
134 | "## Example of attenutation.py ##\n",
135 | "print \"The extinction coefficient for a spherical ice particle of 1 mm diameter at 5 cm\"\n",
136 | "print rmet.attenuation.ext_coeff(1E-3, 5E-2, 1.78-0.0024j)"
137 | ],
138 | "language": "python",
139 | "metadata": {},
140 | "outputs": [
141 | {
142 | "output_type": "stream",
143 | "stream": "stdout",
144 | "text": [
145 | "The extinction coefficient for a spherical ice particle of 1 mm diameter at 5 cm\n",
146 | "1.95154416234e-10\n"
147 | ]
148 | }
149 | ],
150 | "prompt_number": 5
151 | },
152 | {
153 | "cell_type": "code",
154 | "collapsed": false,
155 | "input": [
156 | "## Converting units ##\n",
157 | "print \"25 dBZ in linear units is %g\" % rmet.conversion.dBZ2Z(25.)\n",
158 | "print \"Wind speeds:\"\n",
159 | "print \"20 mph winds in SI units is %g m/s\" % rmet.conversion.mph2si(20.)\n",
160 | "print \"40 kmh winds in SI units is %g m/s\" % rmet.conversion.kmh2si(40.)"
161 | ],
162 | "language": "python",
163 | "metadata": {},
164 | "outputs": [
165 | {
166 | "output_type": "stream",
167 | "stream": "stdout",
168 | "text": [
169 | "25 dBZ in linear units is 316.228\n",
170 | "Wind speeds:\n",
171 | "20 mph winds in SI units is 8.94082 m/s\n",
172 | "40 kmh winds in SI units is 11.1111 m/s\n"
173 | ]
174 | }
175 | ],
176 | "prompt_number": 7
177 | },
178 | {
179 | "cell_type": "code",
180 | "collapsed": false,
181 | "input": [
182 | "## Examples for doppler.py ##\n",
183 | "fmax_min = rmet.doppler.fmax(csapr_prf_min) # Hz\n",
184 | "fmax_max = rmet.doppler.fmax(csapr_prf_max) # Hz\n",
185 | "nyquist_vel_min = rmet.doppler.Vmax(csapr_prf_min, wavelength) # m/s\n",
186 | "nyquist_vel_max = rmet.doppler.Vmax(csapr_prf_max, wavelength) # m/s\n",
187 | "Rmax_min = rmet.doppler.Rmax(csapr_prf_min) # m\n",
188 | "Rmax_max = rmet.doppler.Rmax(csapr_prf_max) # m\n",
189 | "\n",
190 | "# Combining the two PRF for a dual-PRF system\n",
191 | "nyquist_dual = rmet.doppler.Vmax_dual(wavelength, csapr_prf_min, csapr_prf_max) # m/s"
192 | ],
193 | "language": "python",
194 | "metadata": {},
195 | "outputs": [],
196 | "prompt_number": 18
197 | },
198 | {
199 | "cell_type": "code",
200 | "collapsed": false,
201 | "input": [
202 | "# Now again print out the answers to the above cell to check the performance:\n",
203 | "print \"CSAPR max frequency for min PRF = %g Hz\" % (fmax_min)\n",
204 | "print \"CSAPR max frequency for max PRF = %g Hz\" % (fmax_max)\n",
205 | "print \"CSAPR nyquist velocity range = %g - %g m/s\" % (nyquist_vel_min, nyquist_vel_max)\n",
206 | "print \"CSAPR unambiguous range for PRF range = %g - %g km\" % (Rmax_min/1000., Rmax_max/1000.)\n",
207 | "print \"CSAPR dual PRF example = %g m/s\" % nyquist_dual"
208 | ],
209 | "language": "python",
210 | "metadata": {},
211 | "outputs": [
212 | {
213 | "output_type": "stream",
214 | "stream": "stdout",
215 | "text": [
216 | "CSAPR max frequency for min PRF = 100 Hz\n",
217 | "CSAPR max frequency for max PRF = 1350 Hz\n",
218 | "CSAPR nyquist velocity range = 2.4 - 32.4 m/s\n",
219 | "CSAPR unambiguous range for PRF range = 750 - 55.5556 km\n",
220 | "CSAPR dual PRF example = 2.592 m/s\n"
221 | ]
222 | }
223 | ],
224 | "prompt_number": 19
225 | },
226 | {
227 | "cell_type": "code",
228 | "collapsed": false,
229 | "input": [],
230 | "language": "python",
231 | "metadata": {},
232 | "outputs": []
233 | }
234 | ],
235 | "metadata": {}
236 | }
237 | ]
238 | }
--------------------------------------------------------------------------------
/pyradarmet/BeamBlock.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | BeamBlock.py - Class for Beam Blockage calculations
4 |
5 | Author::
6 | Nick Guy - OU CIMMS/Univ of Miami
7 |
8 | This program was ported from code written in IDL used at Colorado State University.
9 | It is believed that the original code at CSU was written by Steven Nesbitt.
10 | Timothy Lang also contributed to the development of this program.
11 |
12 | This program is not particularly fast as it reads in large DEM files.
13 | """
14 | # Import required libraries
15 | import struct
16 |
17 | import numpy as np
18 | import matplotlib.cm as cm
19 | import matplotlib.pyplot as plt
20 | from matplotlib.path import Path
21 | from glob import glob
22 | import os
23 | import subprocess
24 | import netCDF4 as nc4
25 | import time
26 | from pkg_resources import Requirement, resource_filename
27 |
28 | import pyradarmet as rmet
29 | from .geometry import r_effective, ray_height
30 | from .geometry import half_power_radius, range_correct, beam_block_frac
31 | from scipy.io.idl import readsav
32 | import scipy.ndimage as scim
33 | from mpl_toolkits.basemap import Basemap
34 |
35 | ###################################
36 |
37 | VERSION = '0.0.1'
38 |
39 | ##################################
40 | # CONSTANTS #
41 | #############
42 | # Radius of earth (m)
43 | RE = 6371000.
44 |
45 | # Plotting setup
46 | TITLEDICT = {'fontsize': 10}
47 | PAN3_AX1 = [.08, .5, .43, .43]
48 | PAN3_AX2 = [.58, .5, .43, .43]
49 | PAN3_AX3 = [.10, .08, .80, .35]
50 |
51 | # Set up the data directory for DEM files
52 | #DEM_DATADIR = os.sep.join([os.path.dirname(__file__), 'data'])
53 | #print DEM_DATADIR
54 |
55 | class BeamBlock(object):
56 | """
57 | A class instance to calculalate beam blockage of weather radar
58 | """
59 |
60 | ###################################################
61 |
62 | def __init__(self, dem_filename=None, verbose=False,
63 | dem_hdr_file=None, upper_left_x=None, upper_left_y=None,
64 | radar_lon=None, radar_lat=None, radar_alt=None,
65 | radar_antenna_height=None,
66 | elev_angle=None, beamwidth=None, refract_grad=None,
67 | range_dist=None, azimuthal_angle=None,
68 | num_rng_pts=None, num_az_pts=None,
69 | lon_box_size=6., lat_box_size=6.):
70 | """
71 | If initialized with a filename (incl. path), will call
72 | read_bin_netcdf() to populate the class instance.
73 |
74 | If not, it simply instances the class but does not populate
75 | its attributes.
76 |
77 | Parameters::
78 | ----------
79 | dem_filename : str
80 | Full path and filename of Digital Elevation Model file to use.
81 | If not supplied, a file will be selected, along with the
82 | appropriate dem_hdr_file, based on radar_lon and radar_lat
83 | dem_hdr_file : str
84 | Full path and filename of Digital Elevation Model header file to use.
85 | This file will find upper_left_x and upper_left_y.
86 | If not set, upper_left_x and upper_left_y must be set by user.
87 | upper_left_x : float
88 | Coordinate of upper left X position of file [decimal degrees]
89 | Only required if dem_hdr_file not supplied
90 | upper_left_y : float
91 | Coordinate of upper left Y position of file [decimal degrees]
92 | verbose: boolean
93 | Set to True for text output. Useful for debugging.
94 |
95 | radar_lon : float
96 | Radar longitude [decimal degrees]
97 | radar_lat : float
98 | Radar latitude [decimal degrees]
99 | radar_alt : float
100 | Radar altitude [meters]
101 | radar_antenna_height : float
102 | Radar antenna height [meters]
103 | elev_angle : float
104 | Elevation angle for analysis [degrees]
105 | beamwidth : float
106 | Radar beam width [degrees]
107 | refract_grad : float
108 | Vertical gradient of refraction [1/km]
109 | range_dist : float
110 | Radial distance of radar beam [km]
111 | azimuthal angle : float
112 | Azimuthal angle [decimal degrees] to display beam propagation along
113 | num_rng_pts : int
114 | Number of points along the range direction (i.e. # gates)
115 | num_az_pts : int
116 | Number of points along the azimuthal direction (i.e. # angles)
117 | lon_box_size : float
118 | The size of the longitudinal box to subset, radar in middle
119 | lat_box_size : float
120 | The size of the latitudinal box to subset, radar in middle
121 | """
122 | self.dem_file = dem_filename
123 | self.dem_file2 = None
124 | self.dem_hdr_file = dem_hdr_file
125 | self.upper_left_x = upper_left_x
126 | self.upper_left_y = upper_left_y
127 | self.lon_box_size = lon_box_size
128 | self.lat_box_size = lat_box_size
129 | self.ralt = radar_alt
130 |
131 | # Set the directory where DEM data can be found
132 | try:
133 | os.environ["GTOPO_DATA"]
134 | self.demdir = os.path.join(os.environ["GTOPO_DATA"], 'data')
135 | except KeyError:
136 | print "No GTOPO_DATA environmental variable found, "\
137 | "assuming data embedded in PyRadarMet package"
138 | self.demdir = os.sep.join([os.path.dirname(__file__), 'data'])
139 |
140 | # Set radar latitude and longitude
141 | if (radar_lon is None) or (radar_lat is None):
142 | print "Radar longitude, latitude, and altitude need to be defined!"
143 | exit()
144 | else:
145 | self.rlon, self.rlat= radar_lon, radar_lat
146 |
147 | # Set the height of the radar antenna above ground
148 | if radar_antenna_height is None:
149 | self.rheight = 0.
150 | else:
151 | self.rheight = radar_antenna_height
152 |
153 | # Set elevation angle (degrees) [e.g. 0.8, 1.3, 1.8, 2.5]
154 | if elev_angle is None:
155 | self.E = 0.8
156 | else:
157 | self.E = elev_angle
158 |
159 | # Set radar beam width (degrees)
160 | if beamwidth is None:
161 | self.BW = 0.91
162 | else:
163 | self.BW = beamwidth
164 |
165 | # Set the vertical refractivity gradient (1/km) [Standard Atm = -40]
166 | if refract_grad is None:
167 | self.dNdH = -40.
168 | else:
169 | self.dNdH = refract_grad
170 |
171 | # Set the radial distance of radar [range] in km
172 | if range_dist is None:
173 | self.range = 150.
174 | else:
175 | self.range = range_dist
176 |
177 | # Azimuthal direction to plot beam propagation
178 | if azimuthal_angle is None:
179 | self.Az_plot = 0
180 | else:
181 | self.Az_plot = azimuthal_angle
182 |
183 | # Set the number of points along the radial
184 | if num_rng_pts is None:
185 | self.nrng = 601
186 | else:
187 | self.nrng = num_rng_pts
188 |
189 | # Set the number of azimuthal points
190 | if num_az_pts is None:
191 | self.naz = 361
192 | else:
193 | self.naz = num_az_pts
194 |
195 | # Check the DEM file
196 | self._check_dem_file()
197 | # self._grab_dem_file()
198 |
199 | # Read the DEM file
200 | self.read_dem()
201 |
202 | # Process inputs to calculate beam properties
203 | self.calc_beam_blockage()
204 |
205 | ##############
206 |
207 | def help(self):
208 |
209 | _method_header_printout('help')
210 | print 'To define a new MosaicTile(), use instance = MosaicTile().'
211 | print 'then read in a file to populate attributes.'
212 | print 'Available read methods:'
213 | print ' read_mosaic_netcdf():'
214 | print ' read_mosaic_binary():'
215 | print ' Read v1 MRMS mosaics (7/30/2013 & earlier)'
216 | print ' Also v2 MRMS mosaics (7/30/2013 & after)'
217 | print 'Can also use instance = MosaicTile(filepath+name).'
218 | print 'Set binary=True as keyword above if reading an MRMS binary file.'
219 | print 'Other available methods:'
220 | print 'diag(), get_comp(), plot_vert(), plot_horiz(), three_panel_plot()'
221 | print 'subsection(), write_mosaic_binary(), output_composite()'
222 | _method_footer_printout()
223 |
224 | ##########################
225 | # Read data file methods #
226 | ##########################
227 |
228 | def read_dem(self):
229 | '''Read a DEM file if passed or search for correct file
230 | '''
231 | if self.dem_hdr_file is None:
232 | if (self.upper_left_x is None) or (self.upper_left_y is None):
233 | print "Must prived upper_left_x and upper_left_y if \
234 | no DEM header file is supplied"
235 | self.read_gt30_dem()
236 |
237 | def read_gt30_dem(self):
238 | '''
239 | Read in a binary GTOPO30 DEM file
240 | The following code was adapted from an example at:
241 | http://stevendkay.wordpress.com/tag/dem/
242 | '''
243 | if isinstance(self.dem_hdr_file, str) != False:
244 | # wrap header in dict, because the different elements aren't necessarily in the same row
245 | hdr = dict(np.loadtxt(self.dem_hdr_file, dtype='S20'))
246 | byteorder = hdr['BYTEORDER']
247 | pixeltype = hdr['PIXELTYPE']
248 | nrows = int(hdr['NROWS'])
249 | ncols = int(hdr['NCOLS'])
250 | missing = int(hdr['NODATA'])
251 | upper_left_x = float(hdr['ULXMAP'])
252 | upper_left_y = float(hdr['ULYMAP'])
253 | xdim = float(hdr['XDIM'])
254 | ydim = float(hdr['YDIM'])
255 | else:
256 | # From the output_parameters.hdr file or product documentation
257 | byteorder = 'M'
258 | pixeltype = 'UNSIGNEDINT'
259 | upper_left_x = self.upper_left_x
260 | upper_left_y = self.upper_left_y
261 | nrows = 6000
262 | ncols = 4800
263 | missing = -9999.
264 | xdim = 0.00833333333333
265 | ydim = 0.00833333333333
266 |
267 | # Extract GTOPO30 BIL file
268 | fi = open(self.dem_file, "rb")
269 | databin = fi.read()
270 | fi.close()
271 |
272 | # check for pixeltype, this has to be reviewed
273 | if pixeltype == 'SIGNEDINT':
274 | format = 'h'
275 | if pixeltype == 'UNSIGNEDINT':
276 | format = 'H'
277 | # Unpack binary data into a flat tuple z
278 | # check for byteorder
279 | if byteorder == 'M':
280 | s = ">%d%s" % (nrows * ncols, format,)
281 | else:
282 | s = "<%d%s" % (nrows * ncols, format,)
283 |
284 | z = struct.unpack(s, databin)
285 |
286 | topo = np.zeros((nrows, ncols))
287 | for r in range(0, nrows):
288 | for c in range(0, ncols):
289 | elevation = z[((ncols) * r) + c]
290 | if (elevation == 65535 or elevation < 0 or elevation > 20000):
291 | # may not be needed depending on format, and the "magic number"
292 | # value used for 'void' or missing data
293 | elevation = 0.#np.nan
294 | topo[r][c] = float(elevation)
295 |
296 | lat = upper_left_y - ydim * np.arange(nrows)
297 | lon = upper_left_x + xdim * np.arange(ncols)
298 |
299 | # Find indices for a subset of DEM data
300 | londel = self.lon_box_size / 2.
301 | latdel = self.lat_box_size / 2.
302 | lonInd = np.where((lon >= (self.rlon - londel)) & \
303 | (lon <= (self.rlon + londel)))
304 | latInd = np.where((lat >= (self.rlat - latdel)) & \
305 | (lat <= (self.rlat + latdel)))
306 |
307 | lonSub = lon[np.min(lonInd):np.max(lonInd)]
308 | latSub = lat[np.min(latInd): np.max(latInd)]
309 | self.lon, self.lat = np.meshgrid(lonSub, latSub)
310 | self.topo = topo[np.min(latInd):np.max(latInd), np.min(lonInd): np.max(lonInd)]
311 |
312 | self.topo = np.ma.masked_outside(self.topo, 0, 20000)
313 | self.topo = np.ma.masked_invalid(self.topo)
314 |
315 | # Find the elevation of the radar
316 | if self.ralt is None:
317 | radar_lon_index = self._get_array_index(self.rlon, self.lon[0, :])
318 | radar_lat_index = self._get_array_index(self.rlat, self.lat[:, 0])
319 |
320 | radar_alt_map = scim.map_coordinates(self.topo,
321 | np.vstack((radar_lat_index, radar_lon_index)),
322 | prefilter=False)
323 | self.ralt = self.rheight + radar_alt_map
324 |
325 | ###################################################
326 | # Calculation methods #
327 | ###################################################
328 |
329 | def calc_beam_blockage(self):
330 | '''Calculate the properties of the beam for calculation'''
331 | # Initialize arrays
332 | self.inX = np.empty(self.nrng)
333 | self.inY = np.empty(self.nrng)
334 |
335 | self.rng_lon = np.empty([self.naz, self.nrng])
336 | self.rng_lat = np.empty([self.naz, self.nrng])
337 | self.terr = np.empty([self.naz, self.nrng])
338 |
339 | # Points along each ray to calculate beam propagation
340 | # e.g. From 0 to 150 km, with 0.25 km bin spacing
341 | self.rng = np.linspace(0, self.range*1000., self.nrng)
342 |
343 | # Calculate the effective radius
344 | self.Reff = r_effective()
345 |
346 | # Calculate the height of center of beam
347 | self.h = ray_height(self.rng, self.E,
348 | self.ralt, reff=self.Reff)
349 |
350 | # Calculate the beam radius
351 | self.a = half_power_radius(self.rng, self.BW)
352 |
353 | # Calculate rng_gnd (actual distance along ground)
354 | self.rng_gnd = range_correct(self.rng, self.h, self.E)
355 |
356 | # Set up arrays for calculations
357 | self.phis = np.linspace(0, 360, self.naz)
358 |
359 | # Initialize the beam blockage arrays with 0
360 | self.PBB = np.zeros((self.naz, self.nrng))
361 | self.CBB = np.zeros((self.naz, self.nrng))
362 |
363 | for jj, az in enumerate(self.phis):
364 | # Calculate the Lons/Lats along the rng_gnd
365 | self.rng_lat[jj, :] = np.degrees(np.arcsin(np.sin(np.radians(self.rlat)) * \
366 | np.cos(self.rng_gnd / RE) +\
367 | np.cos(np.radians(self.rlat)) * \
368 | np.sin(self.rng_gnd / RE) *\
369 | np.cos(np.radians(az))\
370 | )\
371 | )
372 | self.rng_lon[jj, :] = self.rlon + \
373 | np.degrees(np.arctan2(np.sin(np.radians(az)) * \
374 | np.sin(self.rng_gnd / RE) *\
375 | np.cos(np.radians(self.rlat)), \
376 | np.cos(self.rng_gnd / RE) - \
377 | np.sin(np.radians(self.rlat)) * \
378 | np.sin(np.radians(self.rng_lat[jj, :]))\
379 | )\
380 | )
381 |
382 | # Find the indices for interpolation
383 | for ii, junk in enumerate(self.inX):
384 | self.inX[ii] = self._get_array_index(self.rng_lon[jj, ii], self.lon[0, :])
385 | self.inY[ii] = self._get_array_index(self.rng_lat[jj, ii], self.lat[:, 0])
386 |
387 |
388 | # Interpolate terrain heights to the radar grid
389 | self.terr[jj, :] = scim.map_coordinates(self.topo,
390 | np.vstack((self.inY, self.inX)),
391 | prefilter=False)
392 |
393 | # Calculate PBB along range
394 | self.PBB[jj, :] = beam_block_frac(self.terr[jj, :],
395 | self.h, self.a)
396 |
397 | self.PBB = np.ma.masked_invalid(self.PBB)
398 |
399 | for ii in range(self.nrng):
400 | if ii == 0:
401 | self.CBB[:, ii] = self.PBB[:, ii]
402 | else:
403 | self.CBB[:,ii] = np.fmax([self.PBB[:, ii]], [self.CBB[:, ii-1]])
404 |
405 | self.terr = np.ma.masked_less(self.terr, -1000.)
406 |
407 | ###############
408 | # Get methods #
409 | ###############
410 |
411 | def _get_array_index(self, value, array):
412 | '''Calculate the exact index position within latitude array'''
413 | # Find the spacing
414 | dp = np.absolute(array[1] - array[0])
415 | # Calculate the relative position
416 | pos = np.absolute(value - array[0]) / dp
417 | return pos
418 |
419 | ####################
420 | # DEM file methods #
421 | ####################
422 |
423 | def _check_dem_file(self):
424 | '''Check if dem file is passed and set if not'''
425 | ###Add ability to stitch 2 files together self.rlon - londel
426 | # Set the radar min and max longitude to plot, arbitrary chosen
427 | minlat, maxlat = self.rlat - 4, self.rlat + 4
428 | minlon, maxlon = self.rlon - 4, self.rlon + 4
429 |
430 | # get radar bounding box corner coords
431 | radar_corners = np.array([[minlon, minlat], [minlon, maxlat],
432 | [maxlon, maxlat], [maxlon, minlat]])
433 |
434 | # file list which will take the found hdr files
435 | flist = []
436 |
437 | # iterate over dem files, besides antarcps
438 | for dem_hdr_file in sorted(glob(os.path.join(self.demdir, '[e,w]*.hdr'))):
439 | hdr = dict(np.loadtxt(dem_hdr_file, dtype='S20'))
440 | nrows = int(hdr['NROWS'])
441 | ncols = int(hdr['NCOLS'])
442 | missing = int(hdr['NODATA'])
443 | upper_left_x = float(hdr['ULXMAP'])
444 | upper_left_y = float(hdr['ULYMAP'])
445 | xdim = float(hdr['XDIM'])
446 | ydim = float(hdr['YDIM'])
447 |
448 | lower_right_x = upper_left_x + ncols * xdim
449 | lower_right_y = upper_left_y - nrows * ydim
450 |
451 | # construct dem bounding box
452 | dem_bbox = [[upper_left_x, lower_right_y], [upper_left_x, upper_left_y],
453 | [lower_right_y, upper_left_y], [lower_right_x, lower_right_y]]
454 |
455 | # make matplotlib Path from dem bounding box
456 | dem_bbox_path = Path(dem_bbox)
457 |
458 | # check if radar corner points are inside
459 | inside = dem_bbox_path.contains_points(radar_corners)
460 |
461 | # if inside put file into list
462 | if np.any(inside):
463 | dem_filename, ext = os.path.splitext(dem_hdr_file)
464 | flist.append(dem_filename + '.dem')
465 | if np.all(inside):
466 | break
467 |
468 | # construct gdalbuildvirt command
469 | command = ['gdalbuildvrt', '-overwrite', '-te', str(minlon), str(minlat),
470 | str(maxlon), str(maxlat), os.path.join(self.demdir, 'interim.vrt')]
471 |
472 | for f in flist:
473 | command.append(f)
474 |
475 | # call gdalbuildvirt subprocess
476 | subprocess.call(command)
477 | vrt_file = os.path.join(self.demdir, 'interim.vrt')
478 | dem_file = os.path.join(self.demdir, 'interim.dem')
479 | # call gdalwarp subprocess
480 | command = ["gdalwarp", '-of', 'Ehdr', '-overwrite', vrt_file, dem_file]
481 | subprocess.call(command)
482 |
483 | # set filenames
484 | self.dem_file = dem_file
485 | self.dem_hdr_file = os.path.join(self.demdir, 'interim.hdr')
486 |
487 | if self.dem_file is None:
488 | if (self.rlon >= -180.) and (self.rlon < 180.) and \
489 | (self.rlat >= -90.) and (self.rlat < -60.):
490 | dem_filename = 'antarcps'
491 | elif (self.rlon >= 20.) and (self.rlon < 60.) and \
492 | (self.rlat >= -10.) and (self.rlat < 40.):
493 | dem_filename = 'e020n40'
494 | elif (self.rlon >= 20.) and (self.rlon < 60.) and \
495 | (self.rlat >= 40.) and (self.rlat < 90.):
496 | dem_filename = 'e020n90'
497 | elif (self.rlon >= 20.) and (self.rlon < 60.) and \
498 | (self.rlat >= -60.) and (self.rlat < -10.):
499 | dem_filename = 'e020s10'
500 |
501 | elif (self.rlon >= 60.) and (self.rlon < 100.) and \
502 | (self.rlat >= -10.) and (self.rlat < 40.):
503 | dem_filename = 'e060n40'
504 | elif (self.rlon >= 60.) and (self.rlon < 100.) and \
505 | (self.rlat >= 40.) and (self.rlat < 90.):
506 | dem_filename = 'e060n90'
507 | elif (self.rlon >= 60.) and (self.rlon < 100.) and \
508 | (self.rlat >= -60.) and (self.rlat < -10.):
509 | dem_filename = 'e060s10'
510 | elif (self.rlon >= 60.) and (self.rlon < 120.) and \
511 | (self.rlat >= -90.) and (self.rlat < -60.):
512 | dem_filename = 'e060s60'
513 |
514 | elif (self.rlon >= 100.) and (self.rlon < 140.) and \
515 | (self.rlat >= -10.) and (self.rlat < 40.):
516 | dem_filename = 'e100n40'
517 | elif (self.rlon >= 100.) and (self.rlon < 140.) and \
518 | (self.rlat >= 40.) and (self.rlat < 90.):
519 | dem_filename = 'e100n90'
520 | elif (self.rlon >= 100.) and (self.rlon < 140.) and \
521 | (self.rlat >= -60.) and (self.rlat < -10.):
522 | dem_filename = 'e100s10'
523 | elif (self.rlon >= 120.) and (self.rlon < 180.) and \
524 | (self.rlat >= -90.) and (self.rlat < -60.):
525 | dem_filename = 'e120s60'
526 |
527 | elif (self.rlon >= 140.) and (self.rlon < 180.) and \
528 | (self.rlat >= -10.) and (self.rlat < 40.):
529 | dem_filename = 'e140n40'
530 | elif (self.rlon >= 140.) and (self.rlon < 180.) and \
531 | (self.rlat >= 40.) and (self.rlat < 90.):
532 | dem_filename = 'e140n90'
533 | elif (self.rlon >= 140.) and (self.rlon < 180.) and \
534 | (self.rlat >= -60.) and (self.rlat < -10.):
535 | dem_filename = 'e140s10'
536 |
537 | elif (self.rlon >= 0.) and (self.rlon < 60.) and \
538 | (self.rlat >= -90.) and (self.rlat < -60.):
539 | dem_filename = 'w000s60'
540 |
541 | elif (self.rlon >= -20.) and (self.rlon < 20.) and \
542 | (self.rlat >= -10.) and (self.rlat < 40.):
543 | dem_filename = 'w020n40'
544 | elif (self.rlon >= -20.) and (self.rlon < 20.) and \
545 | (self.rlat >= 40.) and (self.rlat < 90.):
546 | dem_filename = 'w020n90'
547 | elif (self.rlon >= -20.) and (self.rlon < 20.) and \
548 | (self.rlat >= -60.) and (self.rlat < -10.):
549 | dem_filename = 'w020s10'
550 |
551 | elif (self.rlon >= -60.) and (self.rlon < -20.) and \
552 | (self.rlat >= -10.) and (self.rlat < 40.):
553 | dem_filename = 'w060n40'
554 | elif (self.rlon >= -60.) and (self.rlon < -20.) and \
555 | (self.rlat >= 40.) and (self.rlat < 90.):
556 | dem_filename = 'w060n90'
557 | elif (self.rlon >= -60.) and (self.rlon < -20.) and \
558 | (self.rlat >= -60.) and (self.rlat < -10.):
559 | dem_filename = 'w060s10'
560 | elif (self.rlon >= -60.) and (self.rlon < 0.) and \
561 | (self.rlat >= -90.) and (self.rlat < -60.):
562 | dem_filename = 'w060s60'
563 |
564 | elif (self.rlon >= -100.) and (self.rlon < -60.) and \
565 | (self.rlat >= -10.) and (self.rlat < 40.):
566 | dem_filename = 'w100n40'
567 | elif (self.rlon >= -100.) and (self.rlon < -60.) and \
568 | (self.rlat >= 40.) and (self.rlat < 90.):
569 | dem_filename = 'w100n90'
570 | elif (self.rlon >= -100.) and (self.rlon < -60.) and \
571 | (self.rlat >= -60.) and (self.rlat < -10.):
572 | dem_filename = 'w100s10'
573 | elif (self.rlon >= -120.) and (self.rlon < -60.) and \
574 | (self.rlat >= -90.) and (self.rlat < -60.):
575 | dem_filename = 'w120s60'
576 |
577 | elif (self.rlon >= -140.) and (self.rlon < -100.) and \
578 | (self.rlat >= -10.) and (self.rlat < 40.):
579 | dem_filename = 'w140n40'
580 | elif (self.rlon >= -140.) and (self.rlon < -100.) and \
581 | (self.rlat >= 40.) and (self.rlat < 90.):
582 | dem_filename = 'w140n90'
583 | elif (self.rlon >= -140.) and (self.rlon < -100.) and \
584 | (self.rlat >= -60.) and (self.rlat < -10.):
585 | dem_filename = 'w140s10'
586 |
587 | elif (self.rlon >= -180.) and (self.rlon < -140.) and \
588 | (self.rlat >= -10.) and (self.rlat < 40.):
589 | dem_filename = 'w180n40'
590 | elif (self.rlon >= -180.) and (self.rlon < -140.) and \
591 | (self.rlat >= 40.) and (self.rlat < 90.):
592 | dem_filename = 'w180n90'
593 | elif (self.rlon >= -180.) and (self.rlon < -140.) and \
594 | (self.rlat >= -60.) and (self.rlat < -10.):
595 | dem_filename = 'w180s10'
596 | elif (self.rlon >= -180.) and (self.rlon < -120.) and \
597 | (self.rlat >= -90.) and (self.rlat < -60.):
598 | dem_filename = 'w180s60'
599 |
600 | self.dem_file = os.path.join(self.demdir, (dem_filename + ".dem"))
601 | self.dem_hdr_file = os.path.join(self.demdir, (dem_filename + ".hdr"))
602 |
603 | # Need an alternative method here to find points close to boundary
604 | dem_filename2 = None
605 | if dem_filename2 is not None:
606 | print "MAY NEED A SECOND DEM FILE"
607 |
608 | ################
609 | # Plot methods #
610 | ################
611 |
612 | def plot_3panel(self, beam_blockage='complete',
613 | lon_plot_range=2., lat_plot_range=2.,
614 | terrain_cmap=None, beam_blockage_cmap=None,
615 | elev_min=None, elev_max=None,
616 | range_rings=None,
617 | profile_ymin=None, profile_ymax=None,
618 | ht_shade_min=None, ht_shade_max=None,
619 | lat_spacing=None, lon_spacing=None,
620 | saveFig=False):
621 | """
622 | Create a 3-panel plot characterization of beam blockage
623 |
624 | Parameters::
625 | ----------
626 | beam_blockage : str
627 | 'complete' or 'partial' chooses which field to plot
628 | Defaults to complete.
629 | lon_plot_range : float
630 | Horizontal coverage plots will show +/- lon_plot_range
631 | lat_plot_range : float
632 | Horizontal coverage plots will show +/- lat_plot_range
633 | terrain_cmap : str
634 | Matplotlib colormap to use for elevation map
635 | beam_blockage_cmap : str
636 | Matplotlib colormap to use for beam blockage map
637 | elev_min : float
638 | Minumum elevation to display on beam propagation and terrain height [meters]
639 | elev_max : float
640 | Maximum elevation to display on beam propagation and terrain height [meters]
641 | range_rings : float
642 | A list with location of range rings - e.g. [50., 100.]
643 | ht_shade_min : float
644 | Minimum elevation to use in topography colorbar shading [meters]
645 | ht_shade_max : float
646 | Maximum elevation to use in topography colorbar shading [meters]
647 | lat_spacing : float
648 | Spacing to use for latitudinal lines on maps [degrees]
649 | lon_spacing : float
650 | Spacing to use for longitudinal lines on maps [degrees]
651 | saveFig : boolean
652 | True to save figure as output file, False to show
653 | """
654 |
655 | # Set the radar min and max longitude to plot
656 | self.minlat, self.maxlat = self.rlat - lat_plot_range, self.rlat + lat_plot_range
657 | self.minlon, self.maxlon = self.rlon - lon_plot_range, self.rlon + lon_plot_range
658 | if terrain_cmap is None:
659 | terrain_cmap = 'BrBG_r'
660 | if beam_blockage_cmap is None:
661 | beam_blockage_cmap = 'PuRd'
662 | self.terr_cmap, self.bb_cmap = terrain_cmap, beam_blockage_cmap
663 |
664 | fig = plt.figure(figsize=(10,8))
665 | ax1 = fig.add_axes(PAN3_AX1)
666 | ax2 = fig.add_axes(PAN3_AX2)
667 | ax3 = fig.add_axes(PAN3_AX3)
668 |
669 | self.draw_terrain_height_map(fig, ax1, vmin=ht_shade_min, vmax=ht_shade_max,
670 | lat_spacing=lat_spacing, lon_spacing=lon_spacing)
671 | if beam_blockage == 'partial':
672 | self.draw_bb_map(fig, ax2, BB=self.PBB, range_rings=range_rings,
673 | lat_spacing=lat_spacing, lon_spacing=lon_spacing)
674 | elif beam_blockage == 'complete':
675 | self.draw_bb_map(fig, ax2, range_rings=range_rings,
676 | lat_spacing=lat_spacing, lon_spacing=lon_spacing)
677 | self.draw_beam_terrain_profile(fig, ax3, ymin=elev_min, ymax=elev_max)
678 |
679 | if saveFig:
680 | plt.savefig('BBmap.png', format='png')
681 | else:
682 | plt.show()
683 |
684 | def draw_terrain_height_map(self, fig, ax, vmin=None, vmax=None,
685 | lat_spacing=None, lon_spacing=None):
686 | '''Draw the terrain heights'''
687 | if vmin is None:
688 | topomin = 0.05
689 | else:
690 | topomin = vmin/1000.
691 | if vmax is None:
692 | topomax = 3.
693 | else:
694 | topomax = vmax/1000.
695 |
696 | if lat_spacing is None:
697 | lat_spacing = 1.
698 | if lon_spacing is None:
699 | lon_spacing = 1.
700 |
701 | bm1 = Basemap(projection='cea', resolution='l', area_thresh = 10000.,
702 | llcrnrlon=self.minlon, urcrnrlon=self.maxlon,
703 | llcrnrlat=self.minlat, urcrnrlat=self.maxlat,
704 | ax=ax)
705 | ax.set_title('Terrain within %02d km of Radar (km)'%(self.range), fontdict=TITLEDICT)
706 | bm1.drawmeridians(np.arange(self.minlon, self.maxlon, lon_spacing), labels=[1,0,0,1])
707 | bm1.drawparallels(np.arange(self.minlat, self.maxlat, lat_spacing), labels=[1,0,0,1])
708 | bm1.drawcountries()
709 | bm1.drawcoastlines()
710 | bm1.drawrivers()
711 |
712 | xbm1, ybm1 = bm1(self.lon, self.lat)
713 | Htmap = bm1.pcolormesh(xbm1, ybm1, self.topo/1000., vmin=topomin, vmax=topomax,
714 | cmap=self.terr_cmap)
715 | bm1.plot(self.rlon, self.rlat, 'rD', latlon=True)
716 | #plot_range_ring(50., bm=bm1, color='w')
717 | fig.colorbar(Htmap, ax=ax)
718 |
719 | def draw_bb_map(self, fig, ax, BB=None, range_rings=None,
720 | lat_spacing=None, lon_spacing=None):
721 | '''Draw the Beam Blockage'''
722 | if BB is None:
723 | BB = self.CBB
724 |
725 | if lat_spacing is None:
726 | lat_spacing = 1.
727 | if lon_spacing is None:
728 | lon_spacing = 1.
729 |
730 | bm2 = Basemap(projection='cea', resolution='l', area_thresh = 10000.,
731 | llcrnrlon=self.minlon, urcrnrlon=self.maxlon,
732 | llcrnrlat=self.minlat, urcrnrlat=self.maxlat,
733 | ax=ax)
734 | ax.set_title('Beam-blockage fraction', fontdict=TITLEDICT)
735 | bm2.drawmeridians(np.arange(self.minlon, self.maxlon, lon_spacing), labels=[1,0,0,1])
736 | bm2.drawparallels(np.arange(self.minlat, self.maxlat, lat_spacing), labels=[1,0,0,1])
737 | bm2.drawcountries()
738 | bm2.drawcoastlines()
739 | bm2.drawrivers()
740 |
741 | xbm2, ybm2 = bm2(self.rng_lon, self.rng_lat)
742 | BBmap = bm2.pcolormesh(xbm2, ybm2, BB, vmin=0., vmax=1., cmap=self.bb_cmap)
743 | if range_rings is not None:
744 | for nn in range(len(range_rings)):
745 | self.plot_range_ring(range_rings[nn], bm=bm2)
746 | fig.colorbar(BBmap, ax=ax)
747 |
748 | def draw_beam_terrain_profile(self, fig, ax, ymin=None, ymax=None):
749 | '''Draw the Beam height along with terrain and PBB'''
750 | if ymin is None:
751 | ymin = 0.
752 | if ymax is None:
753 | ymax = 5000.
754 |
755 | bc, = ax.plot(self.rng_gnd / 1000., self.h / 1000., '-b',
756 | linewidth=3, label='Beam Center')
757 | b3db, = ax.plot(self.rng_gnd / 1000., (self.h + self.a) / 1000., ':b',
758 | linewidth=1.5, label='3 dB Beam width')
759 | ax.plot(self.rng_gnd / 1000., (self.h - self.a) / 1000., ':b')
760 | tf = ax.fill_between(self.rng_gnd / 1000., 0.,
761 | self.terr[self.Az_plot, :] / 1000.,
762 | color='0.75')
763 | ax.set_xlim(0., self.range)
764 | ax.set_ylim(ymin / 1000., ymax / 1000.)
765 | ax.set_title(r'dN/dh = %d km$^{-1}$; Elevation angle = %g degrees at %d azimuth'% \
766 | (self.dNdH, self.E, self.Az_plot), fontdict=TITLEDICT)
767 | ax.set_xlabel('Range (km)')
768 | ax.set_ylabel('Height (km)')
769 |
770 | axb = ax.twinx()
771 | bbf, = axb.plot(self.rng_gnd / 1000., self.CBB[self.Az_plot, :], '-k',
772 | label='BBF')
773 | axb.set_ylabel('Beam-blockage fraction')
774 | axb.set_ylim(0., 1.)
775 | axb.set_xlim(0., self.range)
776 |
777 | ax.legend((bc, b3db, bbf), ('Beam Center', '3 dB Beam width', 'BBF'),
778 | loc='upper left', fontsize=10)
779 |
780 | def plot_range_ring(self, range_ring_location_km, bm=None,
781 | color='k', ls='-'):
782 | """
783 | Plot a single range ring.
784 | Parameters::
785 | ----------
786 | range_ring_location_km : float
787 | Location of range ring in km.
788 | npts: int
789 | Number of points in the ring, higher for better resolution.
790 | ax : Axis
791 | Axis to plot on. None will use the current axis.
792 | """
793 | npts = 100
794 | bm.tissot(self.rlon, self.rlat,
795 | np.degrees(range_ring_location_km * 1000. / RE), npts,
796 | fill=False, color='black', linestyle='dashed')
797 |
798 | ################
799 | # Save methods #
800 | ################
801 |
802 | def save_map_data(self, filename=None):
803 | """
804 | Save beam blockage and mapped data to NetCDF file
805 |
806 | Parameters::
807 | ----------
808 | filename : str
809 | Output name of NetCDF file
810 | """
811 | if filename is None:
812 | filename = 'BBmap_data'
813 | else:
814 | filename = filename
815 |
816 | # Open a NetCDF file to write to
817 | nc_fid = nc4.Dataset(filename + '.nc', 'w', format='NETCDF4')
818 | nc_fid.description = "PyRadarMet BeamBlockage calculations"
819 |
820 | # Define dimensions
821 | xid = nc_fid.createDimension('range', self.nrng)
822 | yid = nc_fid.createDimension('azimuth', self.naz)
823 |
824 | # Set global attributes
825 | nc_fid.topo_source = 'USGS GTOPO30 DEM files'
826 | nc_fid.history = 'Created ' + time.ctime(time.time()) + ' using python netCDF4 module'
827 |
828 | # Create Output variables
829 | azid = nc_fid.createVariable('azimuth', np.float32, ('azimuth',))
830 | azid.units = 'degrees'
831 | azid.long_name = 'Azimuth'
832 | azid[:] = self.phis[:]
833 |
834 | rngid = nc_fid.createVariable('range', np.float32, ('range',))
835 | rngid.units = 'meters'
836 | rngid.long_name = 'Range'
837 | rngid[:] = self.rng_gnd[:]
838 |
839 | topoid = nc_fid.createVariable('topography', np.float32, ('azimuth','range'))
840 | topoid.units = 'meters'
841 | topoid.long_name = 'Terrain Height'
842 | topoid[:] = self.terr[:]
843 |
844 | lonid = nc_fid.createVariable('longitude', np.float32, ('azimuth', 'range'))
845 | lonid.units = 'degrees east'
846 | lonid.long_name = 'Longitude'
847 | lonid[:] = self.rng_lon[:]
848 |
849 | latid = nc_fid.createVariable('latitude', np.float32, ('azimuth', 'range'))
850 | latid.units = 'degrees north'
851 | latid.long_name = 'Latitude'
852 | latid[:] = self.rng_lat[:]
853 |
854 | pbbid = nc_fid.createVariable('pbb', np.float32, ('azimuth','range'))
855 | pbbid.units = 'unitless'
856 | pbbid.long_name = 'Partial Beam Blockage Fraction'
857 | pbbid[:] = self.PBB[:]
858 |
859 | cbbid = nc_fid.createVariable('cbb', np.float32, ('azimuth','range'))
860 | cbbid.units = 'unitless'
861 | cbbid.long_name = 'Cumulative Beam Blockage Fraction'
862 | cbbid[:] = self.CBB[:]
863 |
864 | print "Saving " + filename + '.nc'
865 | # Close the NetCDF file
866 | nc_fid.close()
867 |
--------------------------------------------------------------------------------
/pyradarmet/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | PyRadarMet - Python Package that contains a variety of functions for radar meteorology
3 | ==================================
4 | Top-level package (:mod:`pyradarmet`)
5 | ==================================
6 |
7 | .. currentmodule:: pyradarmet
8 |
9 |
10 | """
11 | from . import attenuation
12 | from . import conversion
13 | from . import doppler
14 | from . import geometry
15 | from . import system
16 | from . import variables
17 | from . import zdrcal
18 | __all__ = [s for s in dir() if not s.startswith('_')]
19 |
--------------------------------------------------------------------------------
/pyradarmet/attenuation.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | pyradarmet.attenuation
4 | ======================
5 |
6 | Functions to calculate coefficients used with attenuation calculations.
7 |
8 | References
9 | ----------
10 | Battan (1973), Radar Observations of the Atmosphere.
11 | Doviak and Zrnic (1993), Doppler Radar and Weather Observations.
12 | """
13 | import numpy as np
14 |
15 |
16 | def abs_coeff(D, lam, m):
17 | """
18 | Absorption coefficient of a spherical particle. Unitless.
19 |
20 | Doviak and Zrnic (1993), Eqn 3.14a or Battan (1973), Eqn 6.6
21 |
22 | Parameters
23 | ----------
24 | D : float or array
25 | Particle diameter [m]
26 | lam : float
27 | Radar wavelength [m]
28 | m : float
29 | Complex refractive index [unitless], in the form 7.14 - 2.89j
30 |
31 | Notes
32 | -----
33 | An example from Battan (1973) is for water at 0C m=7.14-2.89j for a
34 | wavelength of 3.21 cm and for ice m=1.78-0.0024j for
35 | wavelength range from 1-10 cm.
36 | See Battan (1973) Ch.4 , Tables 4.1 and 4.2 for values from
37 | Gunn and East (1954).
38 | Also see Doviak and Zrnic (1993), Fig. 3.3 caption.
39 | """
40 | Km = (m**2 - 1) / (m**2 + 2)
41 | Qa = (np.pi**2 * np.asarray(D)**3 / lam) * np.imag(-1 * Km)
42 | return Qa
43 |
44 |
45 | def scat_coeff(D, lam, m):
46 | """
47 | Scattering coefficient of a spherical particle. Unitless.
48 |
49 | Doviak and Zrnic (1993), Eqn 3.14b or Battan (1973), Eqn 6.5
50 |
51 | Parameters
52 | ----------
53 | D : float or array
54 | Particle diameter [m]
55 | lam : float
56 | Radar wavelength [m]
57 | m : float
58 | Complex refractive index [unitless]
59 |
60 | Notes
61 | -----
62 | An example from Battan (1973) is for water at 0C m=7.14-2.89j for a
63 | wavelength of 3.21 cm and for ice m=1.78-0.0024j for
64 | wavelength range from 1-10 cm.
65 | See Battan (1973) Ch.4 , Tables 4.1 and 4.2 for values from
66 | Gunn and East (1954).
67 | Also see Doviak and Zrnic (1993), Fig. 3.3 caption.
68 | """
69 |
70 | Km = (m**2 - 1) / (m**2 + 2)
71 | Qs = (2 * np.pi**5 * np.asarray(D)**6 / (3 * lam**4) * (np.absolute(Km))**2)
72 | return Qs
73 |
74 |
75 | def ext_coeff(D, lam, m):
76 | """
77 | Extinction coefficient of a spherical particle. Unitless.
78 |
79 | Doviak and Zrnic (1993), Eqn 3.14b or Battan (1973), Eqn 6.5
80 |
81 | Parameters
82 | ----------
83 | D : float or array
84 | Particle diameter [m]
85 | lam : float
86 | Radar wavelength [m]
87 | m : float
88 | Complex refractive index [unitless]
89 |
90 | USAGE::
91 | -----
92 | Qe = ext_coeff(D,lam,m)
93 |
94 | NOTES::
95 | -----
96 | An example from Battan (1973) is for water at 0C m=7.14-2.89j for a
97 | wavelength of 3.21 cm and for ice m=1.78-0.0024j for
98 | wavelength range from 1-10 cm.
99 | See Battan (1973) Ch.4 , Tables 4.1 and 4.2 for values from
100 | Gunn and East (1954).
101 | Also see Doviak and Zrnic (1993), Fig. 3.3 caption.
102 | """
103 |
104 | Qa = abs_coeff(np.asarray(D), lam, m)
105 | Qs = scat_coeff(np.asarray(D), lam, m)
106 | Qe = Qa + Qs
107 |
108 | return Qe
109 |
--------------------------------------------------------------------------------
/pyradarmet/conversion.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | pyradarmet.conversion
4 | =====================
5 |
6 | Functions for converting common radar units.
7 | """
8 | import numpy as np
9 |
10 |
11 | def dbz2z(dbz):
12 | """
13 | Convert from log [dBZ] to linear Z [mm^6 m^−3] units.
14 |
15 | Parameters
16 | ----------
17 | dBZ : float or array
18 | logarithmic reflectivity value
19 | """
20 | return 10.**(np.asarray(dbz)/10.)
21 |
22 |
23 | def z2dbz(zlin):
24 | """
25 | Convert from linear Z [mm^6 m^−3] to log [dBZ] units.
26 |
27 | Parameters
28 | ----------
29 | zlin : float or array
30 | linear reflectivity units
31 | """
32 | return 10. * np.log10(np.asarray(zlin))
33 |
34 |
35 | def si2kmh(si):
36 | """
37 | Convert from SI [m/s] wind units to km/h.
38 |
39 | Parameters
40 | ----------
41 | si : float or array
42 | Wind in SI units (m/s)
43 | """
44 | return np.asarray(si) * 3600. / 1000.
45 |
46 |
47 | def si2mph(si):
48 | """
49 | Convert from SI wind units to miles/h [mph].
50 |
51 | Parameters
52 | ----------
53 | si: float or array
54 | Wind in SI units (m/s)
55 | """
56 | return np.asarray(si) * 0.62137 / 1000. * 3600.
57 |
58 |
59 | def si2kts(si):
60 | """
61 | Convert from SI wind units to knots [kt].
62 |
63 | Parameters
64 | ----------
65 | si: float or array
66 | Wind in SI units (m/s)
67 | """
68 | return np.asarray(si) * 0.51
69 |
70 |
71 | def kmh2si(kmh):
72 | """
73 | Convert from km/h to SI wind units [m/s].
74 |
75 | Parameters
76 | ----------
77 | kmh: float or array
78 | Wind in km/hr
79 | """
80 | return np.asarray(kmh) * 1000. / 3600.
81 |
82 |
83 | def mph2si(mph):
84 | """
85 | Convert from miles/h to SI wind units [m/s].
86 |
87 | Parameters
88 | ----------
89 | mph: float or array
90 | Wind in miles per hour
91 | """
92 | return np.asarray(mph) * 1000. / (0.62137 * 3600.)
93 |
94 |
95 | def kts2si(kts):
96 | """
97 | Convert from knots to SI wind units [m/s].
98 |
99 | Parameters
100 | ----------
101 | kts: float or array
102 | Wind in knots
103 | """
104 | return np.asarray(kts) / 0.51
105 |
--------------------------------------------------------------------------------
/pyradarmet/doppler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | pyradarmet.doppler
4 | ==================
5 |
6 | Functions to calculate radar characteristics for Doppler radar.
7 |
8 | References
9 | ----------
10 | Rinehart (1997), Radar for Meteorologists.
11 | Jorgensen (1983; JCAM), Feasibility Test of an Airborne Pulse-Doppler
12 | Meteorological Radar.
13 | """
14 | import numpy as np
15 |
16 |
17 | speed_of_light = 3e8 # Speed of light [m/s]
18 |
19 | def freq(lam):
20 | """Frequency [Hz] given wavelength.
21 |
22 | Parameters
23 | ----------
24 | lam : float or array
25 | Wavelength [m]
26 | """
27 | return speed_of_light / np.asarray(lam)
28 |
29 |
30 | def wavelength(freq):
31 | """Wavelength [m] given frequency.
32 |
33 | Parameters
34 | ----------
35 | freq : float or array
36 | Frequency [Hz]
37 | """
38 | return speed_of_light / np.asarray(freq)
39 |
40 |
41 | def pulse_duration(tau):
42 | """Pulse duration [s] from pulse length.
43 |
44 | Parameters
45 | ----------
46 | tau : float or array
47 | Pulse length [m]
48 | """
49 | return 2 * np.asarray(tau) / speed_of_light
50 |
51 |
52 | def pulse_length(pdur):
53 | """Pulse length [m] from pulse duration.
54 |
55 | Parameters
56 | ----------
57 | pDur : float or array
58 | Pulse duration [s]
59 | """
60 | return speed_of_light * np.asarray(pdur) / 2
61 |
62 |
63 | def fmax(prf):
64 | """Maximum frequency [Hz] given PRF.
65 |
66 | From Rinehart (1997), Eqn 6.8
67 |
68 | Parameters
69 | ----------
70 | PRF : float or array
71 | Pulse repetition frequency [Hz]
72 | """
73 | return np.asarray(prf) / 2.
74 |
75 |
76 | def Vmax(PRF, lam):
77 | """Nyquist velocity, or maximum unambiguous Doppler velocity (+ or -) [m/s].
78 |
79 | From Rinehart (1997), Eqn 6.7
80 |
81 | Parameters
82 | ----------
83 | PRF : float or array
84 | Radar pulse repetition frequency [Hz]
85 | lam : float or array
86 | Radar wavelength [m]
87 | """
88 | return np.asarray(PRF) * lam / 4.
89 |
90 |
91 | def Rmax(PRF):
92 | """Maximum unamiguous range [m].
93 |
94 | From Rinehart (1997), Eqn 6.11
95 |
96 | Parameters
97 | ----------
98 | PRF : float or array
99 | Pulse repetition frequency [Hz]
100 | """
101 | return speed_of_light / (2. * np.asarray(PRF))
102 |
103 |
104 | def doppler_dilemma(varin, lam):
105 | """
106 | The "Doppler dilemma" is the fact that both the Nyquist velocity and
107 | unambiguous maximum range of the radar are based upon the PRF of the system.
108 |
109 | However, they are inversely proportional, meaning that increasing one
110 | requires a decrease in the other. A trade-off inherent in Doppler radar
111 | systems. This relationship allows a solution for one variable given the
112 | value of the other.
113 |
114 | From Rinehart (1997), Eqn 6.12
115 |
116 | Parameters
117 | ----------
118 | varin : float or array
119 | Nyquist Velocity [m/s] or Maximum unambiguous range [m]
120 | lam : float
121 | Radar wavelength [m]
122 | """
123 | return (speed_of_light * lam / 8.) / np.asarray(varin)
124 |
125 | ########################
126 | ## MOBILE PLATFORMS ##
127 | ########################
128 |
129 | def Vshift(ground_speed, psi):
130 | """
131 | Adjusted Doppler velocity [m/s] from a mobile platform.
132 | Shift in Doppler velocity from mobile perspective.
133 |
134 | Jorgensen (1983), Eqn 2
135 |
136 | Parameters
137 | ----------
138 | ground_speed : float or array
139 | Gound speed [m/s]
140 | psi : float or array
141 | Angle between actual azimuth and fore/aft angle [deg]
142 |
143 | Notes
144 | -----
145 | In the case of a mobile platform (such as the NOAA P-3 aircraft, the
146 | Doppler velocity must be adjusted for movement of the scanning platform.
147 |
148 | The fore/aft angle is defined as the angle fore or aft from a plane
149 | normal to the direction of motion
150 | """
151 | len_gs = len(np.asarray(ground_speed))
152 | len_psi = len(np.asarray(psi))
153 | # if len_gs != len_psi and len_gs != 1 and len_psi != 1:
154 | return np.asarray(ground_speed) * np.cos(np.deg2rad(psi))
155 |
156 |
157 | def Vmax_dual(lam, prf1, prf2):
158 | """Doppler velocity [m/s] from dual PRF scheme radar (+ or -).
159 |
160 | From Jorgensen (1983), Eqn 2
161 |
162 | Parameters
163 | ----------
164 | lam : float
165 | Radar wavelength [m]
166 | prf1 : float
167 | First Pulse repetition frequency [Hz]
168 | prf2 : float
169 | Second Pulse repetition frequency [Hz]
170 |
171 | Notes
172 | -----
173 | In the case of a mobile platform (such as the NOAA P-3 aircraft, the
174 | Doppler velocity must be adjusted for movement of the scanning platform.
175 |
176 | The fore/aft angle is defined as the angle fore or aft from a plane
177 | normal to the direction of motion
178 | """
179 |
180 | Vmax = lam / (4 * ((1. / prf1) - (1. / prf2)))
181 |
182 | return Vmax
183 |
184 |
--------------------------------------------------------------------------------
/pyradarmet/geometry.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | pyradarmet.geometry
4 | ===================
5 |
6 | Functions to calculate radar geometry characteristics.
7 |
8 | References
9 | ----------
10 | Rinehart (1997), Radar for Meteorologists.
11 | Battan (1973), Radar Observations of the Atmosphere.
12 | Bech et al. (2003; JAOT), The Sensitivity of Single Polarization Weather Radar
13 | Beam Blockage Correction to Variability in the Vertical Refractivity Gradient.
14 | """
15 | import numpy as np
16 |
17 |
18 | earth_radius = 6371000 # Earth's average radius [m] assuming sphericity
19 | r43 = earth_radius * 4./3. # 4/3 Approximation effective radius for standard atmosphere [m]
20 |
21 | # Earth radius taken according to International Union of Geodesy and Geophysics
22 |
23 | def r_effective(dndh=-39e-6):
24 | """
25 | Effective radius [m] calculation.
26 |
27 | Rinehart (1997), Eqn 3.9, solved for R'
28 |
29 | Parameters
30 | ----------
31 | dndh : float
32 | Refraction [N x10^-6/km]
33 |
34 | Notes
35 | -----
36 | Effective radius of earth given a refraction. If no refraction is given
37 | a "standard atmosphere" is assumed, the valued needed to have straight
38 | radar rays.
39 | """
40 | # Convert earth's radius to km for common dN/dH values and then
41 | # multiply by 1000 to return radius in meters
42 | return (1. / ((1/(earth_radius/1000.)) + (dndh))) * 1000.
43 |
44 |
45 | def half_power_radius(r, bwhalf):
46 | """
47 | Half-power radius [m].
48 |
49 | Battan (1973),
50 |
51 | Parameters
52 | ----------
53 | r : float or array
54 | Range [m]
55 | bwhalf : float
56 | Half-power beam width [degrees]
57 | """
58 | # Convert earth's radius to km for common dN/dH values and then
59 | # multiply by 1000 to return radius in meters
60 | return (np.asarray(r) * np.deg2rad(bwhalf)) / 2.
61 |
62 |
63 | def ray_height(r, elev, h0, reff=r43):
64 | """
65 | Center of radar beam height [m] calculation.
66 |
67 | Rinehart (1997), Eqn 3.12, Bech et al. (2003) Eqn 3
68 |
69 | Parameters
70 | ----------
71 | r : float or array
72 | Range from radar to point of interest [m]
73 | elev : float
74 | Elevation angle of radar beam [deg]
75 | h0 : float
76 | Height of radar antenna [m]
77 | reff : float
78 | Effective radius
79 |
80 | Notes
81 | -----
82 | If no Effective radius is given a "standard atmosphere" is assumed,
83 | the 4/3 approximation.
84 |
85 | Bech et al. (2003) use a factor ke that is the ratio of earth's radius
86 | to the effective radius (see r_effective function) and Eqn 4 in B03
87 | """
88 | # Convert earth's radius to km for common dN/dH values and then
89 | # multiply by 1000 to return radius in meters
90 | term1 = (np.sqrt(np.asarray(r)**2 +reff**2 +
91 | 2 * np.asarray(r) * reff * np.sin(np.deg2rad(elev))))
92 | h = term1 - reff + h0
93 | return h
94 |
95 |
96 | def sample_vol_ideal(r, bw_h, bw_v, pulse_length):
97 | """
98 | Idealized Sample volume [m^3] assuming all power in half-power beamwidths.
99 |
100 | From Rinehart (1997), Eqn 5.2
101 |
102 | Parameters
103 | ----------
104 | r : float or array
105 | Distance to sample volume from radar [m]
106 | bw_h : float
107 | Horizontal beamwidth [deg]
108 | bw_v : float
109 | Vertical beamwidth deg]
110 | pulse_length : float
111 | Pulse length [m]
112 |
113 | Notes
114 | -----
115 | This form assumes all transmitted energy is in the half-power beamwidths.
116 | A more realistic solution is found in the sample_vol_gauss function
117 | """
118 | return (np.pi * (np.asarray(r) * np.deg2rad(bw_h)/2.) * (np.asarray(r) *
119 | np.deg2rad(bw_v)/2.) * (pulse_length/2.))
120 |
121 |
122 | def sample_vol_gauss(r, bw_h, bw_v, pulse_length):
123 | """
124 | Sample volume [m^3] assuming transmitted energy in Gaussian beam shape.
125 |
126 | From Rinehart (1997), Eqn 5.4
127 |
128 | Parameters
129 | ----------
130 | r : float or array
131 | Distance to sample volume from radar [m]
132 | bw_h : float
133 | Horizontal beamwidth [deg]
134 | bw_v : float
135 | Vertical beamwidth deg]
136 | pulse_length : float
137 | Pulse length [m]
138 |
139 | Notes
140 | -----
141 | This form assumes a Gaussian beam shape for transmitted energy and is more
142 | realistic than the sample_vol_ideal. Derived by Probert-Jones (1962).
143 | """
144 | Numer = np.pi * np.asarray(r)**2 * np.deg2rad(bw_h) * np.deg2rad(bw_v) * pulse_length
145 | Denom = 16. * np.log(2)
146 |
147 | SVol = Numer / Denom
148 | return SVol
149 |
150 |
151 | def range_correct(r, h, elev):
152 | """
153 | A corrected range [m] from radar that takes into account the "loss" of
154 | ground distance because of the radar elevation angle. This is a
155 | cumulative effect at each gate along the ray.
156 |
157 | From CSU Radar Meteorology AT 741 Notes
158 |
159 | Parameters
160 | ----------
161 | r : float or array
162 | Distance to sample volume from radar [m]
163 | h : float
164 | Height of the center of radar volume [m]
165 | elev : float
166 | Elevation angle [deg]
167 |
168 | Notes
169 | -----
170 | This function requires that an array be passed! If you need just one
171 | point create a 2 element array with a begin point.
172 | This is now set up to only accept a 1D array I believe. May need to
173 | fix this in the future.
174 | """
175 | # Calculate the change in height along the ray
176 | dh1 = h[1:] - h[:-1]
177 | # Add the 0th place in the ray at the beginning
178 | dh2 = np.insert(dh1, 0, h[0])
179 |
180 | # Calculate the change in distance at each gate
181 | a90r = np.pi/2. # 90 degrees in radians
182 | dr = dh2 / (np.tan(a90r - np.deg2rad(elev)))
183 |
184 | # Now calculate the corrected range at each gate
185 | rnew = np.asarray(r) - np.cumsum(dr)
186 | return rnew
187 |
188 |
189 | def beam_block_frac(Th, Bh, a):
190 | """Partial beam blockage fraction. Unitless.
191 |
192 | From Bech et al. (2003), Eqn 2 and Appendix
193 |
194 | Parameters
195 | ----------
196 | Th : float
197 | Terrain height [m]
198 | Bh : float
199 | Beam height [m]
200 | a : float
201 | Half power beam radius [m]
202 |
203 | Notes
204 | -----
205 | This procedure uses a simplified interception function where no vertical
206 | gradient of refractivity is considered. Other algorithms treat this
207 | more thoroughly. However, this is accurate in most cases other than
208 | the super-refractive case.
209 |
210 | See the the half_power_radius function to calculate variable a
211 |
212 | The heights must be the same units!
213 | """
214 |
215 | # First find the difference between the terrain and height of
216 | # radar beam (Bech et al. (2003), Fig.3)
217 | y = Th - Bh
218 |
219 | Numer = (y * np.sqrt(a**2 - y**2)) + (a**2 * np.arcsin(y/a)) + (np.pi * a**2 /2.)
220 |
221 | Denom = np.pi * a**2
222 |
223 | PBB = Numer / Denom
224 |
225 | return PBB
226 |
--------------------------------------------------------------------------------
/pyradarmet/system.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | pyradarmet.system
4 | =================
5 |
6 | Functions to calculate radar system characteristics.
7 |
8 | References
9 | ----------
10 | Rinehart (1997), Radar for Meteorologists.
11 | """
12 | import numpy as np
13 |
14 |
15 | speed_of_light = 3e8 # Speed of light [m/s]
16 | kBoltz = 1.381e-23 # Boltzmann's constant [ m^2 kg s^-2 K^-1]
17 |
18 | def gain_Pratio(p1, p2):
19 | """
20 | Antenna gain [dB] via power ratio.
21 |
22 | From Rinehart (1997), Eqn 2.1
23 |
24 | Parameters
25 | ----------
26 | p1 : float or array
27 | Power on the beam axis [W]
28 | p2 : float or array
29 | Power from an isotropic antenna [W]
30 |
31 | Notes
32 | -----
33 | Ensure that both powers have the same units!
34 | If arrays are used, either for one okay, or both must be the same length.
35 | """
36 | return 10. * np.log10(np.asarray(p1) / np.asarray(p2))
37 |
38 |
39 | def freq(lam):
40 | """
41 | Frequency [Hz] given wavelength.
42 |
43 | Parameters
44 | ----------
45 | lam : float or array
46 | Wavelength [m]
47 | """
48 | return speed_of_light / np.asarray(lam)
49 |
50 |
51 | def wavelength(freq):
52 | """
53 | Wavelength [m] given frequency.
54 |
55 | Parameters
56 | ----------
57 | freq : float or array
58 | Frequency [Hz]
59 | """
60 | return speed_of_light / np.asarray(freq)
61 |
62 |
63 | def pulse_length(pdur):
64 | """
65 | Pulse length [m] given pulse duration.
66 |
67 | Parameters
68 | ----------
69 | pDur : float or array
70 | Pulse duration [s]
71 |
72 | Notes
73 | -----
74 | This equation is only interested in return pulses to radar, leading
75 | to the factor of 1/2.
76 | """
77 | return speed_of_light * np.asarray(pdur) / 2.
78 |
79 |
80 | def pulse_duration(tau):
81 | """
82 | Pulse duration [s] from pulse length.
83 |
84 | Parameters
85 | ----------
86 | tau : float or array
87 | Pulse length [m]
88 | """
89 | return 2 * np.asarray(tau) / speed_of_light
90 |
91 |
92 | def radar_const(power_t, gain, tau, lam, bw_h, bw_v, aloss, rloss):
93 | """
94 | Radar constant. Unitless.
95 |
96 | From CSU Radar Meteorology notes, AT 741
97 |
98 | Parameters
99 | ----------
100 | power_t : float
101 | Transmitted power [W]
102 | gain : float
103 | Antenna Gain [dB]
104 | tau : float
105 | Pulse Width [s]
106 | lam : float
107 | Radar wavelength [m]
108 | bw_h : float
109 | Horizontalntenna beamwidth [degrees]
110 | bw_v : float
111 | Vertical antenna beamwidth [degrees]
112 | aloss : float
113 | Antenna/waveguide/coupler loss [dB]
114 | rloss : float
115 | Receiver loss [dB]
116 | """
117 | # Convert from dB to linear units
118 | alosslin = 10**(aloss / 10.)
119 | rlosslin = 10**(rloss / 10.)
120 | gainlin = 10**(gain / 10.)
121 |
122 | # Convert beamwidth to radians
123 | bw_hr = np.deg2rad(bw_h)
124 | bw_vr = np.deg2rad(bw_v)
125 |
126 | # Calculate the numerator
127 | Numer = (np.pi**3 * speed_of_light * power_t * gainlin**2 * tau *
128 | bw_hr * bw_vr * alosslin * rlosslin)
129 |
130 | # Calculate the denominator
131 | Denom = 1024. * np.log(2) * lam**2
132 | return Numer/Denom
133 |
134 |
135 | def ant_eff_area(gain, lam):
136 | """
137 | Antenna effective area. [m^-2]
138 |
139 | From Rinehart (1997), Eqn 4.5
140 |
141 | Parameters
142 | ----------
143 | gain : float or array
144 | Antenna Gain [dB]
145 | lam : float
146 | Radar wavelength [m]
147 | """
148 | # Convert from dB to linear units
149 | gainlin = 10**(np.asarray(gain) / 10.)
150 | return gainlin * lam**2 / (4 * np.pi)
151 |
152 |
153 | def power_target(power_t, gain, areat, r):
154 | """
155 | Power [W] intercepted by target.
156 |
157 | From Rinehart (1997), Eqn 4.3
158 |
159 | Parameters
160 | ----------
161 | power_t : float
162 | Transmitted power [W]
163 | gain : float
164 | Antenna gain [dB]
165 | areat : float
166 | Area of target [m^2]
167 | r : float or array
168 | Distance to sample volume from radar [m]
169 | """
170 | # Convert from dB to linear units
171 | gainlin = 10**(gain / 10.)
172 | return (power_t * gainlin * areat) / (4 * np.pi * np.asarray(r)**2)
173 |
174 |
175 | def xsec_bscatter_sphere(diam, lam, dielectric=0.93):
176 | """
177 | Backscatter cross-sectional area [m^-2] of a sphere using the Rayleigh approximation.
178 |
179 | From Rinehart (1997), Eqn 4.9 and 5.7
180 |
181 | Parameters
182 | ----------
183 | diam : float or array
184 | Diamter of target [m]
185 | lam : float
186 | Radar wavelength [m]
187 | dielectric : float
188 | Dielectric factor [unitless]
189 |
190 | Notes
191 | -----
192 | The Rayleigh approximation is good when the diamter of a spherical particle
193 | is much smaller than the wavelength of the radar (D/wavelength= 1/16). This
194 | condition leads to the relationship that the area is proportional to the
195 | sixth power of the diameter.
196 |
197 | The default is for a dielectric factor value for water. This can be
198 | changed by the user, e.g. K=0.208 for particle sizes of equivalent melted
199 | diameters or K=0.176 for particle sizes of equivalent ice spheres.
200 | """
201 | return (np.pi**5 * dielectric**2 * np.asarray(diam)**6) / lam**4
202 |
203 |
204 | def norm_xsec_bscatter_sphere(diam, lam, dielectric=0.93):
205 | """
206 | Normalized Backscatter cross-sectional area [m^2] of a sphere using the Rayleigh approximation.
207 |
208 | From Rinehart (1997), Eqn 4.9 and 5.7 and Battan Ch. 4.5
209 |
210 | Parameters
211 | ----------
212 | diam : float or array
213 | Diamter of targer [m]
214 | lam : float
215 | Radar wavelength [m]
216 | dielectric : float
217 | Dielectric factor [unitless]
218 |
219 | Notes
220 | -----
221 | The Rayleigh approximation is good when the diamter of a spherical particle
222 | is much smaller than the wavelength of the radar (D/wavelength= 1/16). This
223 | condition leads to the relationship that the area is proportional to the
224 | sixth power of the diameter.
225 |
226 | The default is for a dielectric factor value for water. This can be
227 | changed by the user, e.g. K=0.208 for particle sizes of equivalent melted
228 | diameters or K=0.176 for particle sizes of equivalent ice spheres.
229 | """
230 |
231 | # Calculate the cross-sectional backscatter area
232 | sig = xsec_bscatter_sphere(np.asarray(diam), lam, dielectric)
233 | return sig/ (np.pi * (np.asarray(diam)/2.)**2)
234 |
235 |
236 | def size_param(diam, lam):
237 | """
238 | Size parameter calculation. Unitless.
239 |
240 | From Rinehart (1997), Eqn 4.9 and 5.7 and Battan Ch. 4.5
241 |
242 | Parameters
243 | ----------
244 | diam : float or float
245 | Diamter of target [m]
246 | lam : float
247 | Radar wavelength [m]
248 |
249 | Notes
250 | -----
251 | The size paramter can be used along with the backscattering cross-section to
252 | distinguish ice and water dielectric characteristics. For example:
253 | Alpha < 2 the backscattering cross-section of ice is smaller than water,
254 | Alpha > 2 the opposite is true due to the fact that absorption in water
255 | exceeds that in ice.
256 | """
257 | return 2 * np.pi * np.asarray(diam)/2. / lam
258 |
259 |
260 | def power_return_target(power_t, gain, lam, sig, r):
261 | """
262 | Power [W] returned y target located at the center of the antenna beam pattern.
263 |
264 | From Rinehart (1997), Eqn 4.7
265 |
266 | Parameters
267 | ----------
268 | power_t : float
269 | Transmitted power [W]
270 | gain : float
271 | Antenna gain [dB]
272 | lam : float
273 | Radar wavelength [m]
274 | sig : float
275 | Backscattering cross-sectional area of target [m^2]
276 | r : float or array
277 | Distance to sample volume from radar [m]
278 | """
279 | # Convert from dB to linear units
280 | gainlin = 10**(gain/10.)
281 | return ((power_t * gainlin**2 * lam**2 * sig) /
282 | (64 * np.pi**3 * np.asarray(r)**4))
283 |
284 |
285 | def thermal_noise(bandwidth, Units, noise_temp=290.):
286 | """
287 | Thermal noise power [W or 'dBm'].
288 |
289 | From CSU Radar Meteorology notes, AT741
290 |
291 | Parameters
292 | ----------
293 | bandwidth : float or array
294 | Receiver bandwidth [Hz]
295 | Units : float
296 | String of nits desired, can be 'W' or 'dBm'
297 | Ts : float
298 | Reciever noise temperature [K]
299 |
300 | Notes
301 | -----
302 | Reciever noise temp set to conventional 290K by default
303 | """
304 | # Calculate the noise, convert if requested
305 | noise = kBoltz * noise_temp * np.asarray(bandwidth)
306 |
307 | if Units.upper()=='W':
308 | noiset = noise
309 | elif Units.upper()=='DBM':
310 | noiset = 10. * np.log10(noise/10**-3)
311 | else:
312 | print("Units must be in 'W' or 'dBm'")
313 | noiset = np.nan
314 | return noiset
315 |
--------------------------------------------------------------------------------
/pyradarmet/variables.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | pyradarmet.variables
4 | ====================
5 |
6 | Functions to calculate radar-derived variables.
7 |
8 | References
9 | ----------
10 | Rinehart (1997), Radar for Meteorologists.
11 | Aydin et al. (1986; JCAM), Remote Sensing of Hail with Dual Linear
12 | Polarization Radar
13 | """
14 | import numpy as np
15 |
16 |
17 | def reflectivity(power_t, gain, pulse_width, wavelength, bw_h, bw_v,
18 | aloss, rloss, power_return, r, dielectric=0.93):
19 | """
20 | Radar reflectivity [mm^6/m^3].
21 |
22 | From Rinehart (1993), Eqn 5.17 (See Eqn 5.14-5.16 also)
23 |
24 | Parameters
25 | ----------
26 | power_t : float
27 | Transmitted power [W]
28 | gain : float
29 | Antenna Gain [dB]
30 | pulse_width : float
31 | Pulse Width [s]
32 | wavelength : float
33 | Radar wavelength [m]
34 | bwidth : float
35 | Antenna beamwidth [degrees]
36 | aloss : float
37 | Antenna/waveguide/coupler loss [dB]
38 | rloss : float
39 | Receiver loss [dB]
40 | dielectric : float
41 | Dielectric factor [unitless]
42 | power_return : float
43 | Returned power [W]
44 | r : float or array
45 | Range to target [m]
46 |
47 | Notes
48 | -----
49 | This routine calls the radar_constant function.
50 | The default is for a dielectric factor value for water. This can be
51 | changed by the user, e.g. K=0.208 for particle sizes of equivalent melted
52 | diameters or K=0.176 for particle sizes of equivalent ice spheres.
53 | """
54 | # Call the radar constant function
55 | C1 = radar_constant(power_t, gain, pulse_width, wavelength, bw_h, bw_v, aloss, rloss)
56 | return power_return * np.asarray(r)**2 / (C1 * dielectric**2)
57 |
58 |
59 | def radial_velocity(frequency, wavelength):
60 | """
61 | Radial velocity [m/s].
62 |
63 | From Rinehart (1997), Eqn 6.6
64 |
65 | Parameters
66 | ----------
67 | frequency : float or array
68 | Frequency shift [Hz]
69 | wavelength : float or array
70 | Radar wavelength [m]
71 |
72 | Notes
73 | -----
74 | If arrays are used, either for one okay, or both must be the same length.
75 | """
76 | return np.asarray(frequency) * np.asarray(wavelength) / 2.
77 |
78 |
79 | def cdr(refl_parallel, refl_orthogonal):
80 | """
81 | Circular depolarization ratio [dB].
82 |
83 | From Rinehart (1997), Eqn 10.2
84 |
85 | Parameters
86 | ----------
87 | refl_parallel : float or array
88 | Reflectivity in the parallel channel [mm^6/m^3]
89 | refl_orthogonal : float or array
90 | Reflectivity in the orthogonal channel [mm^6/m^3]
91 |
92 | Notes
93 | -----
94 | Ensure that both powers have the same units!
95 |
96 | Radars that transmit right-hand circular polarization and receive and
97 | receive both left- and right-hand circular polarization (using two
98 | antennas) and acquiring the same pulse.
99 | The parallel (orthogonal) component refers to the same
100 | (opposite) polarization as transmitted. Non-spericity of hydrometeors
101 | may be detected (inf long, thin scatterers have CDR = 0 dB, while perfect
102 | spheres have CDR = -infinity
103 |
104 | Can also use power measurements instead of reflectivity.
105 |
106 | If arrays are used, either for one okay, or both must be the same length.
107 | """
108 | return 10. * np.log10(np.asarray(refl_parallel)/np.asarray(refl_orthogonal))
109 |
110 |
111 | def ldr(z_h, z_v):
112 | """
113 | Linear depolarization ratio [dB].
114 |
115 | From Rinehart (1997), Eqn 10.3
116 |
117 | Parameters
118 | ----------
119 | z_h : float or array
120 | Horizontal reflectivity [mm^6/m^3]
121 | z_v : float or array
122 | Vertical reflectivity [mm^6/m^3]
123 |
124 | Notes
125 | -----
126 | Ensure that both powers have the same units!
127 |
128 | Uses both polarizations in a dual-pol radar from a single pulse.
129 |
130 | Perfect spheres yield LDR => -infinity (though antenna limitations limit
131 | LDR values to -40 dB for small spheres.
132 | Long, thin targets, LDR => 0
133 |
134 | Typical values in the range -15 > LDR > -35 dB
135 |
136 | If arrays are used, either for one okay, or both must be the same length.
137 | """
138 | return 10. * np.log10(np.asarray(z_h) / np.asarray(z_v))
139 |
140 |
141 | def zdr(z_h, z_v):
142 | """
143 | Differential reflectivity [dB].
144 |
145 | From Rinehart (1997), Eqn 10.3 and Seliga and Bringi (1976)
146 |
147 | Parameters
148 | ----------
149 | z_h : float or array
150 | Horizontal reflectivity [mm^6/m^3]
151 | z_v : float or array
152 | Vertical reflectivity [mm^6/m^3]
153 |
154 | Notes
155 | -----
156 | Ensure that both powers have the same units!
157 |
158 | Alternating horizontally and linearly polarized pulses are averaged.
159 |
160 | Notes
161 | -----
162 | If arrays are used, either for one okay, or both must be the same length.
163 | """
164 | return 10. * np.log10(np.asarray(z_h) / np.asarray(z_v))
165 |
166 |
167 | def zdp(z_h, z_v):
168 | """
169 | Reflectivity difference [dB].
170 |
171 | From Rinehart (1997), Eqn 10.3
172 |
173 | Parameters
174 | ----------
175 | z_h : float
176 | Horizontal reflectivity [mm^6/m^3]
177 | z_v : float
178 | Horizontal reflectivity [mm^6/m^3]
179 |
180 | Notes
181 | -----
182 | Ensure that both powers have the same units!
183 |
184 | Alternating horizontally and linearly polarized pulses are averaged.
185 | """
186 | zh = np.atleast_1d(z_h)
187 | zv = np.atleast_1d(z_v)
188 | if len(zh) != len(zv):
189 | raise ValueError('Input variables must be same length')
190 | return
191 |
192 | zdp = np.full_like(zh, np.nan)
193 | good = np.where(zh > zv)
194 | zdp[good] = 10.* np.log10(zh[good] - zv[good])
195 | return zdp
196 |
197 |
198 | def hdr(dbz_h, zdr):
199 | """
200 | Differential reflectivity [dB] hail signature.
201 |
202 | From Aydin et al. (1986), Eqns 4-5
203 |
204 | Parameters
205 | ----------
206 | dbz_h : float or array
207 | Horizontal reflectivity [dBZ]
208 | zdr : float or array
209 | Differential reflectivity [dBZ]
210 |
211 | Notes
212 | -----
213 | Ensure that both powers have the same units!
214 |
215 | Positive HDR and strong gradients at edges signify ice. The larger HDR,
216 | the greater likelihood that ice is present.
217 |
218 | Considerations for this equation (see paper for more details):
219 | 1) Standar error of disdrometer data allowed for
220 | 2) Drop oscillation accounted for based on 50% model of Seliga et al (1984)
221 | 3) Lower (27) bound chose to provide constant Zh ref level
222 | 4) Upper cutoff of 60 (may be too low)
223 |
224 | Picca and Ryzhkof (2012) mention that this does not take into account
225 | the hail melting process. So use at your own risk!
226 | """
227 | zdr = np.atleast_1d(zdr)
228 | # Set the f(zdr) based upon observations
229 | f = np.full_like(zdr, np.nan)
230 | negind = np.where(zdr <= 0)
231 | lowind = np.where((zdr > 0) & (zdr <= 1.74))
232 | highind = np.where(zdr > 1.74)
233 | f[negind] = 27.
234 | f[lowind] = 19. * zdr[lowind] + 27.
235 | f[highind] = 60.
236 | # Calculate HDR
237 | return np.asarray(dbz_h) - f
238 |
--------------------------------------------------------------------------------
/pyradarmet/zdrcal.py:
--------------------------------------------------------------------------------
1 | """
2 | pyradarmet.zdrcal
3 | =========================
4 |
5 | Calculate the differential reflectivity (Zdr) offset from polarimetric radars.
6 |
7 | Adapted by Nick Guy from original Fortran code written by David Jorgensen,
8 | implemented more robust processing to reduce noise in data. Thanks to Joseph
9 | Hardin regarding processing and posting a notebook online that got this started.
10 | http://www.josephhardinee.com/blog/?p=35
11 |
12 |
13 | .. autosummary::
14 | :toctree: generated/
15 |
16 | calculate_zdr_offset
17 |
18 |
19 | """
20 | import numpy as np
21 | import matplotlib.pylab as plt
22 |
23 | import pyart
24 | #from .conversion import dBZ2Z, Z2dBZ
25 | #======================================================================
26 | def calculate_zdr_offset(radar, debug=False,
27 | remove_first_n_gates=None, sample_min=100,
28 | rhv_min=None, sig_min=0.5,Htmax=None, dbz_min=None,
29 | zdr_field=None, refl_field=None, phidp_field=None,
30 | rhv_field=None, kdp_field=None):
31 | """
32 | Differential reflectivity calibration from 'birdbath' scans.
33 |
34 | Parameters
35 | ----------
36 | radar : Radar
37 | Radar object (from PyArt) to use for attenuation calculations. Must have
38 | copol_coeff, norm_coherent_power, proc_dp_phase_shift,
39 | reflectivity_horizontal fields.
40 | debug : bool
41 | True to print debugging information, False supressed this printing.
42 |
43 | Returns
44 | -------
45 | zdr_bias : dict
46 | Field dictionary containing the differential reflectivity statistics.
47 |
48 | Other Parameters
49 | ----------------
50 | remove_first_n_gates : float
51 | Number of gates at the beginning of each ray to to remove from the
52 | calculation.
53 | sample_min : int
54 | The minimum number of samples required to perform calculation.
55 | rhv_min : float
56 | Minimum copol_coeff value to consider valid.
57 | sig_min : float
58 | Minimum standard deviation to consider valid and include in calculations.
59 | dbz_min : float
60 | Minimum reflectivity to consider valid and include in calculations.
61 | Htmax : float
62 | Maximum height [km] to use in bias calculations.
63 | zdr_field, refl_field, rhv_field, phidp_field, kdp_field : str
64 | Field names within the radar object which represent the horizonal
65 | reflectivity, normal coherent power, the copolar coefficient, and the
66 | differential phase shift. A value of None for any of these parameters
67 | will use the default field name as defined in the Py-ART
68 | configuration file.
69 |
70 | Usage
71 | ----------
72 |
73 | """
74 | # Pull out some information to send back in zdr_bias dictionary
75 | Rlat = radar.latitude['data'] # Radar latitude
76 | Rlon = radar.longitude['data'] # Radar longitude
77 | RAlt = radar.altitude['data'] # Radar altitude
78 | RadName = radar.metadata['instrument_name'] # Radar name
79 | SitName = radar.metadata['source'] # Site/project name
80 | GenName = radar.metadata['institution'] # Facilitiy name
81 | time = radar.time # Time at middle of each ray
82 | range = radar.range # Range along ray
83 | azimuth = radar.azimuth # Azimuth of scan
84 |
85 | # Create 2D arrays for masking data later
86 | # Ht2D, az2D = np.meshgrid(range['data']/1000.,azimuth['data'])
87 | # print radar.fields.keys()
88 | # parse the field parameters
89 | if zdr_field is None:
90 | zdr_field = pyart.config.get_field_name('differential_reflectivity')
91 | if zdr_field not in radar.fields:
92 | zdr_field = pyart.config.get_field_name('ZDR')
93 | if refl_field is None:
94 | refl_field = pyart.config.get_field_name('reflectivity')
95 | if refl_field not in radar.fields:
96 | refl_field = pyart.config.get_field_name('DBZ')
97 | if rhv_field is None:
98 | rhv_field = pyart.config.get_field_name('cross_correlation_ratio')
99 | if rhv_field not in radar.fields:
100 | rhv_field = pyart.config.get_field_name('RHOHV')
101 | if phidp_field is None:
102 | # use corrrected_differential_phae or unfolded_differential_phase
103 | # fields if they are available, if not use differential_phase field
104 | phidp_field = pyart.config.get_field_name('corrected_differential_phase')
105 | if phidp_field not in radar.fields:
106 | phidp_field = pyart.config.get_field_name('unfolded_differential_phase')
107 | if phidp_field not in radar.fields:
108 | phidp_field = pyart.config.get_field_name('differential_phase')
109 | if phidp_field not in radar.fields:
110 | phidp_field = pyart.config.get_field_name('PHIDP')
111 | if kdp_field is None:
112 | kdp_field = pyart.config.get_field_name('specific_differential_phase')
113 |
114 | # Extract data fields
115 | ZDR = radar.fields[zdr_field]['data']
116 | dBZ = radar.fields[refl_field]['data']
117 | Rhohv = radar.fields[rhv_field]['data']
118 | PhiDP = radar.fields[phidp_field]['data']
119 | KDP = radar.fields[kdp_field]['data']
120 |
121 | # Extract parameters from radar
122 | # nsweeps = int(radar.nsweeps)
123 | nGates = int(radar.ngates) # Number of gates
124 | nrays = int(radar.nrays) # Number of rays
125 |
126 | # Apply mask across variables for missing data
127 | ZDR, dBZ, Rhv, PhiDP, KDP = mask_variables(ZDR, dBZ, Rhohv, PhiDP, KDP)
128 |
129 | # Find corresponding axes for range (height) and azimuth
130 | axAz, axHt = get_ray_range_dims(ZDR, nrays)
131 |
132 | # Apply mask for chosen gates nearest to radar and above height threshold
133 | ZDR, dBZ, Rhv, PhiDP, KDP = mask_gates(ZDR, dBZ, Rhv, PhiDP,KDP,
134 | range['data']/1000., azimuth['data'], axAz,
135 | Htmax=Htmax, remove_first_n_gates=remove_first_n_gates)
136 |
137 | # Apply mask for reflectivity below a threshold (proxy for low SNR)
138 | ZDR, dBZ, Rhv, PhiDP, KDP = mask_refl(ZDR, dBZ, Rhv, PhiDP, KDP, mindBZ=dbz_min)
139 |
140 | # Find individual ray properties (along each ray at azimuthal step)
141 | DR_RaySum, DR_RayAvg, DR_RayStd, NGood_Ray = calc_stats(ZDR, axAz)
142 |
143 | # Find individual range properties (along a constant distance [height] from radar)
144 | DR_HtSum, DR_HtAvg, DR_HtStd, NGood_Ht = calc_stats(ZDR, axHt)
145 | RH_HtSum, RH_HtAvg, RH_HtStd, NGood_RH_Ht = calc_stats(Rhv, axHt)
146 | dBZ_HtSum, dBZ_HtAvg, dBZ_HtStd, NGood_dBZ_Ht = calc_stats(dBZ, axHt)
147 | KDP_HtSum, KDP_HtAvg, KDP_HtStd, NGood_KDP_Ht = calc_stats(KDP, axHt)
148 |
149 | # Apply mask for data with range averaged Std Dev and azimuthal averaged Std Dev
150 | # greater than threshold
151 | ZDR, dBZ, Rhv, PhiDP, KDP = mask_sigma(ZDR, dBZ, Rhv, PhiDP, KDP, DR_HtStd, DR_RayStd,
152 | axAz, minSig=sig_min)
153 |
154 | # Find volume properites
155 | DR_Avg = np.ma.mean(ZDR)
156 | DR_Std = np.ma.std(ZDR, ddof=1)
157 | NGood = np.ma.count(ZDR)
158 |
159 | if (NGood > sample_min):
160 | # Find statistics for filtered (masked) variables
161 | DR_HtSum_filt, DR_HtAvg_filt, DR_HtStd_filt, NGood_Ht_filt = calc_stats(ZDR, axHt)
162 | RH_HtSum_filt, RH_HtAvg_filt, RH_HtStd_filt, NGood_RH_Ht_filt = calc_stats(Rhv, axHt)
163 | dBZ_HtSum_filt, dBZ_HtAvg_filt, dBZ_HtStd_filt, NGood_dBZ_Ht_filt = calc_stats(dBZ, axHt)
164 | KDP_HtSum_filt, KDP_HtAvg_filt, KDP_HtStd_filt, NGood_KDP_Ht_filt = calc_stats(KDP, axHt)
165 | PhiDP_HtSum_filt, PhiDP_HtAvg_filt, PhiDP_HtStd_filt, NGood_PhiDP_Ht_filt = calc_stats(PhiDP, axHt)
166 |
167 | # Create an output dictionary
168 | zdr_bias = create_dict(DR_Avg, DR_Std, NGood,
169 | DR_RaySum, DR_RayAvg, DR_RayStd, NGood_Ray,
170 | DR_HtAvg, DR_HtStd, NGood_Ht,
171 | RH_HtAvg, dBZ_HtAvg, KDP_HtAvg,
172 | ZDR, DR_HtAvg_filt, DR_HtStd_filt,
173 | RH_HtAvg_filt, dBZ_HtAvg_filt, KDP_HtAvg_filt, PhiDP_HtAvg_filt,
174 | RadName, GenName, Rlat, Rlon, RAlt, time, range, azimuth)
175 |
176 | else:
177 | zdr_bias = None
178 | # Send back the dictionary containing data.
179 | return zdr_bias
180 | #======================================================================
181 | def mask_variables(ZDR, dBZ, RhoHV, PhiDP, KDP, rhv_min=0.8):
182 | # Combine the masks for all variables to ensure no missing data points
183 | maskZDR = np.ma.getmask(ZDR)
184 | maskZ = np.ma.getmask(dBZ)
185 | maskRhv = np.ma.getmask(RhoHV)
186 | maskPdp = np.ma.getmask(PhiDP)
187 | is_cor = RhoHV < rhv_min # Mask values where Correl Coeff < threshold
188 |
189 | mskt1 = np.logical_and(maskZ, maskZDR) # Combine refl and ZDR masks
190 | mskt2 = np.logical_and(mskt1, maskRhv) # Combine with missing Rho HV mask
191 | mskt3 = np.logical_and(mskt2, is_cor) # Combine with min threshold Rho HV
192 | mask = np.logical_and(mskt3, maskPdp) # Combine with missing Phidp mask
193 |
194 | ZDR = np.ma.masked_where(mask, ZDR)
195 | dBZ = np.ma.masked_where(mask, dBZ)
196 | Rhv = np.ma.masked_where(mask, RhoHV)
197 | PhiDP = np.ma.masked_where(mask, PhiDP)
198 | KDP = np.ma.masked_where(mask, KDP)
199 |
200 | return ZDR, dBZ, Rhv, PhiDP, KDP
201 | #======================================================================
202 | def get_ray_range_dims(ZDR, nrays):
203 | if nrays == ZDR.shape[0]:
204 | axAz = 1
205 | axHt = 0
206 | elif nrays == ZDR.shape[1]:
207 | axAz = 0
208 | axHt = 1
209 | else:
210 | print "Makes sure you are using polar coordinate data!"
211 | return
212 | return axAz, axHt
213 | #======================================================================
214 | def mask_gates(ZDR, dBZ, Rhv, PhiDP, KDP,
215 | Ht, Az, axAz, Htmax=10., remove_first_n_gates=None):
216 | # Create 2D arrays for masking data later
217 | Ht2D, az2D = np.meshgrid(Ht, Az)
218 |
219 | # Check to see what gate to start with user can set or default to 0
220 | if remove_first_n_gates is None:
221 | GateBeg=1
222 | elif remove_first_n_gates == 0:
223 | GateBeg=1
224 | else:
225 | GateBeg=remove_first_n_gates # because of 0 based indexing
226 |
227 | # Check which axis to perform calculations over
228 | # Mask out the lower gates based upon remove_first_n_gates keyword
229 | if axAz == 1:
230 | # Mask lower gates
231 | ZDR[:, 0:GateBeg-1] = np.ma.masked
232 | dBZ[:, 0:GateBeg-1] = np.ma.masked
233 | Rhv[:, 0:GateBeg-1] = np.ma.masked
234 | PhiDP[:, 0:GateBeg-1] = np.ma.masked
235 | KDP[:, 0:GateBeg-1] = np.ma.masked
236 |
237 | # Mask upper gates
238 | ZDR = np.ma.masked_where(Ht2D > Htmax, ZDR)
239 | dBZ = np.ma.masked_where(Ht2D > Htmax, dBZ)
240 | Rhv = np.ma.masked_where(Ht2D > Htmax, Rhv)
241 | PhiDP = np.ma.masked_where(Ht2D > Htmax, PhiDP)
242 | KDP = np.ma.masked_where(Ht2D > Htmax, KDP)
243 | elif axAz == 0:
244 | ZDR[0:GateBeg-1, :] = np.ma.masked
245 | dBZ[0:GateBeg-1, :] = np.ma.masked
246 | Rhv[0:GateBeg-1, :] = np.ma.masked
247 | PhiDP[0:GateBeg-1, :] = np.ma.masked
248 | KDP[0:GateBeg-1, :] = np.ma.masked
249 |
250 | # Mask upper gates
251 | ZDR = np.ma.masked_where(Ht2D.transpose() > Htmax, ZDR)
252 | dBZ = np.ma.masked_where(Ht2D.transpose() > Htmax, dBZ)
253 | Rhv = np.ma.masked_where(Ht2D.transpose() > Htmax, Rhv)
254 | PhiDP = np.ma.masked_where(Ht2D.transpose() > Htmax, PhiDP)
255 | KDP = np.ma.masked_where(Ht2D.transpose() > Htmax, KDP)
256 |
257 | return ZDR, dBZ, Rhv, PhiDP, KDP
258 | #======================================================================
259 | def mask_sigma(ZDR, dBZ, Rhv, PhiDP, KDP, HtStd, AzStd, axAz, minSig=0.5):
260 |
261 | # Create 2D arrays for masking data later
262 | HtStd2D, RayStd2D = np.meshgrid(HtStd, AzStd)
263 |
264 | # Check which axis to perform calculations over
265 | # Mask out the lower gates based upon remove_first_n_gates keyword
266 | if axAz == 1:
267 | # Mask along the range (height)
268 | ZDR = np.ma.masked_where(HtStd2D > minSig, ZDR)
269 | dBZ = np.ma.masked_where(HtStd2D > minSig, dBZ)
270 | Rhv = np.ma.masked_where(HtStd2D > minSig, Rhv)
271 | PhiDP = np.ma.masked_where(HtStd2D > minSig, PhiDP)
272 | KDP = np.ma.masked_where(HtStd2D > minSig, KDP)
273 |
274 | # Mask along the Azimuths
275 | ZDR = np.ma.masked_where(RayStd2D > minSig, ZDR)
276 | dBZ = np.ma.masked_where(RayStd2D > minSig, dBZ)
277 | Rhv = np.ma.masked_where(RayStd2D > minSig, Rhv)
278 | PhiDP = np.ma.masked_where(RayStd2D > minSig, PhiDP)
279 | KDP = np.ma.masked_where(RayStd2D > minSig, KDP)
280 | elif axAz == 0:
281 | # Mask along the range (height)
282 | ZDR = np.ma.masked_where(HtStd2D.transpose() > minSig, ZDR)
283 | dBZ = np.ma.masked_where(HtStd2D.transpose() > minSig, dBZ)
284 | Rhv = np.ma.masked_where(HtStd2D.transpose() > minSig, Rhv)
285 | PhiDP = np.ma.masked_where(HtStd2D.transpose() > minSig, PhiDP)
286 | KDP = np.ma.masked_where(HtStd2D.transpose() > minSig, KDP)
287 |
288 | # Mask along the Azimuths
289 | ZDR = np.ma.masked_where(RayStd2D.transpose() > minSig, ZDR)
290 | dBZ = np.ma.masked_where(RayStd2D.transpose() > minSig, dBZ)
291 | Rhv = np.ma.masked_where(RayStd2D.transpose() > minSig, Rhv)
292 | PhiDP = np.ma.masked_where(RayStd2D.transpose() > minSig, PhiDP)
293 | KDP = np.ma.masked_where(RayStd2D.transpose() > minSig, KDP)
294 |
295 | return ZDR, dBZ, Rhv, PhiDP, KDP
296 | #======================================================================
297 | def mask_refl(ZDR, dBZ, Rhv, PhiDP, KDP, mindBZ=-40.):
298 | # Mask out the points with reflectivity < mindBZ
299 | ZDR = np.ma.masked_where(dBZ < mindBZ, ZDR)
300 | dBZ = np.ma.masked_where(dBZ < mindBZ, dBZ)
301 | Rhv = np.ma.masked_where(dBZ < mindBZ, Rhv)
302 | PhiDP = np.ma.masked_where(dBZ < mindBZ, PhiDP)
303 | KDP = np.ma.masked_where(dBZ < mindBZ, KDP)
304 |
305 | return ZDR, dBZ, Rhv, PhiDP, KDP
306 | #======================================================================
307 | def calc_stats(Var, Ax):
308 | Sum = np.ma.cumsum(Var, axis=Ax)
309 | Avg = np.ma.mean(Var, axis=Ax)
310 | Std = np.ma.std(Var, axis=Ax)
311 | nValid = np.ma.count(Var, axis=Ax)
312 |
313 | return Sum, Avg, Std, nValid
314 | #======================================================================
315 | def create_dict(DR_Avg, DR_Std, NGood,
316 | DR_RaySum, DR_RayAvg, DR_RayStd, NGood_Ray,
317 | DR_HtAvg, DR_HtStd, NGood_Ht,
318 | RH_HtAvg, dBZ_HtAvg, KDP_HtAvg,
319 | ZDR, DR_HtAvg_filt, DR_HtStd_filt,
320 | RH_HtAvg_filt, dBZ_HtAvg_filt, KDP_HtAvg_filt, PhiDP_HtAvg_filt,
321 | RadName, GenName, Rlat, Rlon, RAlt, time, range, azimuth):
322 |
323 | zdr_bias = {'volume_average': DR_Avg,
324 | 'volume_standard_deviation': DR_Std,
325 | 'volume_number_good': NGood,
326 | 'ray_cumulative_sum': DR_RaySum,
327 | 'ray_average': DR_RayAvg,
328 | 'ray_standard_deviation': DR_RayStd,
329 | 'ray_number_good': NGood_Ray,
330 | 'height_average': DR_HtAvg,
331 | 'height_standard_deviation': DR_HtStd,
332 | 'height_number_good': NGood_Ht,
333 | 'rhv_height_average': RH_HtAvg,
334 | 'dbz_height_average': dBZ_HtAvg,
335 | 'kdp_height_average': KDP_HtAvg,
336 | 'filtered_zdr': ZDR,
337 | 'height_average_filt': DR_HtAvg_filt,
338 | 'height_standard_deviation_filt': DR_HtStd_filt,
339 | 'rhv_height_average_filt': RH_HtAvg_filt,
340 | 'dbz_height_average_filt': dBZ_HtAvg_filt,
341 | 'kdp_height_average_filt': KDP_HtAvg_filt,
342 | 'phidp_height_average_filt': PhiDP_HtAvg_filt,
343 | 'radar_name': RadName,
344 | 'facility_name': GenName,
345 | 'radar_latitude':Rlat,
346 | 'radar_longitude':Rlon,
347 | 'radar_altitude':RAlt,
348 | 'time': time,
349 | 'range': range,
350 | 'azimuth':azimuth}
351 |
352 | return zdr_bias
353 | #======================================================================
354 | def plot_zdr_rhv(X1, X2, Y, xlims1=None, xlims2=None, ymax=None,
355 | label=True, labelTx=' ', labelpos=None):
356 | """Create a vertical plot of differential reflectivity and copolar correlation
357 | coefficient.
358 | """
359 | fig, ax = plt.subplots()
360 | axes = [ax, ax.twiny()]
361 |
362 | if xlims1 is None:
363 | xlims1 = (-1.5, 1.5)
364 | if xlims2 is None:
365 | xlims2 = (.8, 1.)
366 | if ymax is None:
367 | ymax = 10.
368 |
369 | axes[0].plot(X1, Y, label='Differential Reflectivity', color='b')
370 | axes[1].plot(X2, Y, label='CoPolar Correlation Coefficient', color='r')
371 | axes[0].set_xlim(xlims1)
372 | axes[1].set_xlim(xlims2)
373 | axes[0].set_xlabel('Differential Reflectivity (dB)')
374 | axes[1].set_xlabel('CoPolar Correlation Coefficient')
375 | axes[0].tick_params(axis='x', colors='b')
376 | axes[1].tick_params(axis='x', colors='r')
377 | axes[0].xaxis.label.set_color('b')
378 | axes[1].xaxis.label.set_color('r')
379 | axes[0].set_ylim(0., ymax)
380 | axes[0].set_ylabel('Altitude (km)')
381 | axes[0].vlines(0, 0, ymax)
382 | axes[0].xaxis.grid(color='b', ls=':', lw=1)
383 | axes[1].xaxis.grid(color='r', ls=':', lw=1)
384 | axes[0].yaxis.grid(color='k', ls=':', lw=.5)
385 |
386 | handles1, labels1 = axes[0].get_legend_handles_labels()
387 | handles2, labels2 = axes[1].get_legend_handles_labels()
388 | plt.legend([handles1[0], handles2[0]],
389 | ['Differential Reflectivity','CoPolar Correlation Coefficient'],
390 | loc=6, fontsize =11)
391 | # Add label to lower left in plot unless location specified
392 | if label:
393 | if labelpos is None:
394 | labelpos = (.15, .15)
395 | plt.figtext(labelpos[0], labelpos[1], labelTx)
396 |
397 | return fig, ax, axes
398 | #======================================================================
399 | def oplot_zdr_rhv(X1, X2, Y, ax=None, alf=1.):
400 | """Add additional differential reflectivity and copolar correlation
401 | coefficient lines to existing graphic."""
402 | ax[0].plot(X1, Y, color='b', alpha=alf)
403 | ax[1].plot(X2, Y, color='r', alpha=alf)
404 | #======================================================================
405 | def plot_add_stats(instance=None, avg=None, sd=None, points=None,
406 | good_samples=None, tot_samples=None, labelpos=None):
407 | """Add text to the figure that contains statistics."""
408 | if labelpos is None:
409 | labelpos=(.15, .7)
410 | if instance == 'single':
411 | plt.figtext(labelpos[0], labelpos[1],
412 | "Avg Zdr = %g\nStd Dev = %g\nPoints = %g\n"%(avg, sd, points))
413 | elif instance == 'multi':
414 | plt.figtext(labelpos[0], labelpos[1],
415 | "Avg Zdr = %g\nStd Dev = %g\nSamples = %g used of %g total\nPoints = %g\n"%
416 | (avg, sd, good_samples, tot_samples, points))
417 | else:
418 | print "Need to supply either single or multiple run statistics!!"
419 |
420 | #======================================================================
421 | def plot_3panel(X1, X2, X3, Y,
422 | xlims1=None, xlims2=None, xlims3=None, ymax=None,
423 | X1lab='dBZ', X2lab='ZDR', X3lab=r'Kdp (dB km$^{-1}$)', ylab=None,
424 | label=True, labelTx=' ', labelpos=None):
425 | """Create a 3-panel vertical plot of reflectivity, differential reflectivity,
426 | and specific differential phase.
427 | """
428 | fig, (ax1, ax2, vax3) = plt.subplots(1, 3, sharey=True)
429 |
430 | if xlims1 is None:
431 | xlims1 = (-20, 30.)
432 | if xlims2 is None:
433 | xlims2 = (-1.5, 1.5)
434 | if xlims3 is None:
435 | xlims3 = (.2, .4)
436 | if ymax is None:
437 | ymax = 10.
438 | ax1.plot(X1, Y, color='k')
439 | ax2.plot(X2, Y, color='k')
440 | ax3.plot(X3, Y, color='k')
441 | ax1.set_xlim(xlims1)
442 | ax2.set_xlim(xlims2)
443 | ax3.set_xlim(xlims3)
444 | ax1.set_xlabel(X1lab)
445 | ax2.set_xlabel(X2lab)
446 | ax3.set_xlabel(X3lab)
447 |
448 | # Set y axis characteristics
449 | ax1.set_ylim(0., ymax)
450 | if ylab is None:
451 | ax1.set_ylabel('Altitude (km)')
452 | else:
453 | ax1.set_ylabel(ylab)
454 | ax1.vlines(0,0,ymax)
455 | ax2.vlines(0,0,ymax)
456 | ax1.xaxis.grid(color='k', ls=':', lw=1)
457 | ax2.xaxis.grid(color='k', ls=':', lw=1)
458 | ax3.xaxis.grid(color='k', ls=':', lw=1)
459 | ax1.yaxis.grid(color='k', ls=':', lw=.5)
460 | ax2.yaxis.grid(color='k', ls=':', lw=.5)
461 | ax3.yaxis.grid(color='k', ls=':', lw=.5)
462 | # Add label to above plot unless location specified
463 | if label:
464 | if labelpos is None:
465 | labelpos = (.15,.91)
466 | plt.figtext(labelpos[0], labelpos[1], labelTx)
467 |
468 | return fig, ax1, ax2, ax3
469 | #======================================================================
470 | def oplot_3panel(X1, X2, X3, Y, ax1=None, ax2=None, ax3=None, alf=1.):
471 | """Add additional differential reflectivity and copolar correlation
472 | coefficient lines to existing graphic."""
473 | ax1.plot(X1, Y, color='k', alpha=alf)
474 | ax2.plot(X2, Y, color='k', alpha=alf)
475 | ax3.plot(X3, Y, color='k', alpha=alf)
476 | #======================================================================
--------------------------------------------------------------------------------
/scripts/README:
--------------------------------------------------------------------------------
1 | This scripts directory contains executables that may be useful for radar processing.
2 |
3 | ----------------------------------------------------------------------------------
4 | CALCULATIONS/ANALYSIS:
5 | cal_zdr - Lets the user calculate the ZDR bias of a dual polarimetric radar.
6 | zdral_3panel - Produces vertical plots of reflectivity, differential reflectivity,
7 | and standard deviation of ZDR. These can be modified by user.
8 | ----------------------------------------
9 | These programs can be used from the command line in one of two ways.
10 | Note: Just substitute zdrcal_3panel for cal_zdr
11 | For a single file, if you want a plot output:
12 |
13 | $ cal_zdr --{filetype} -p "{filename}"
14 |
15 | See the help file for filetypes accepted and the filename can be a long path.
16 |
17 | For multiple files:
18 |
19 | $ cal_zdr --{filetype} -p "{filename_search_path}"
20 |
21 | the filename_search_path can be something like KTLX* and all files in that
22 | directory will be processed.
23 |
24 | There is a check for ZDRCal, but not sure how robust it is at this time. Note this has
25 | been commented out as there is little consistency in naming conventions
26 | between radar systems.
27 |
28 | The executable can be modified with 3 different thresholds at the top of the
29 | file to control what data to keep for analysis.
30 | ----------------------------------------------------------------------------------
31 | LIST SIGMET RAW FILE CHARACTERISTICS:
32 | listfile_raw.py - Create a list of characteristics for file(s) and output to screen
33 | or output file
34 |
35 | $ listfile_raw.py -m "{filename_search_path}" "{output_filename}"
--------------------------------------------------------------------------------
/scripts/cal_zdr:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 |
3 | #======================================================================
4 | # Import needed libraries
5 | import pyart
6 | import argparse
7 | import numpy as np
8 | import os
9 | import matplotlib.pylab as plt
10 | from glob import glob
11 |
12 | from pyradarmet.zdrcal import calculate_zdr_offset as zdrcal
13 | from pyradarmet.zdrcal import plot_zdr_rhv,plot_add_stats,oplot_zdr_rhv
14 | #======================================================================
15 | # The user can modify these next variables to control how the program processes data
16 | MINSIG = 0.5 # Maximum standard deviation to accept in analysis
17 | MINSAMPLE = 100 # Minimum number of samples used to perform calculations
18 | MINDBZ = 5. # Minimum reflectivity to accept in analysis (proxy for SNR)
19 | GATESKIP = 7 # The number of gates at the beginning or array (closest to radar) to cut out
20 | MINRHV = 0.85 # Minimum correlation coefficient to accept in analysis
21 | MAXHT = 5. # Maximum height to accept in analysis
22 | ZH_LIMS = [-20., 30.] # Range of Reflectivity to plot
23 | RH_LIMS = [0.8, 1.0] # Range of Copolar Correlation Coefficient to plot
24 | DR_LIMS = [-1.5, 1.5] # Range of Differential reflectivity to plot
25 | KD_LIMS = [0.2, 0.4] # Range of Specific Differential Phase to plot
26 |
27 | pOut = "zdr_bias"
28 | pType = "png"
29 | #======================================================================
30 |
31 | if __name__ == '__main__':
32 |
33 | # parse the arguments
34 | parser = argparse.ArgumentParser(
35 | description='Print differential reflectivity calibration statistics.')
36 | parser.add_argument('searchstring', type=str, help='radar file(s) for calculation, if more than one file quotations are needed')
37 |
38 | # group = parser.add_mutually_exclusive_group()
39 |
40 | igroup = parser.add_argument_group(
41 | title='ingest method, optional',
42 | description=('The method of file ingest can be specified. '
43 | 'If no ingest is specified, the format of the file will '
44 | 'be used to determine the best ingest method. '
45 | 'Specify only one of the following:'))
46 |
47 | igroup.add_argument('--sigmet', action='store_true',
48 | help='Sigmet/IRIS ingest')
49 | igroup.add_argument('--mdv', action='store_true', help='MDV ingest')
50 | igroup.add_argument('--cfradial', action='store_true',
51 | help='CF/Radial ingest')
52 | igroup.add_argument('--rsl', action='store_true',
53 | help='RSL ingest')
54 | igroup.add_argument('--nexrad_archive', action='store_true',
55 | help='NEXRAD level 2 archive ingest')
56 | igroup.add_argument('--nexrad_cdm', action='store_true',
57 | help='NEXRAD level 2 CDM ingest')
58 |
59 | parser.add_argument('-v', '--version', action='version',
60 | version='Py-ART version %s' % (pyart.__version__))
61 |
62 | parser.add_argument('-p', '--plot', action='store_true',
63 | help='create an output plot file')
64 | # parser.add_argument('-t', '--text', action='store_true',
65 | # help='create and output text file')
66 |
67 | args = parser.parse_args()
68 | # ======================================================================
69 |
70 | # Search for the file(s)
71 | flist = glob(args.searchstring)
72 |
73 | multiStat = False
74 | if len(flist) > 1:
75 | multiStat = True
76 | # Create an array to hold data, only really used if multiple files
77 | Stats = np.ma.empty([len(flist),3])
78 |
79 | index = 0
80 | indexGood = 0
81 | OutTx = open('zdr_offset.out','w')
82 | for file in flist:
83 | fName, fileExt = os.path.splitext(file)
84 | # read in the file
85 | if args.sigmet:
86 | radar = pyart.io.read_sigmet(file)
87 | elif args.mdv:
88 | radar = pyart.io.read_mdv(file)
89 | elif args.cfradial:
90 | radar = pyart.io.read_cfradial(file)
91 | elif args.rsl:
92 | radar = pyart.io.read_rsl(file)
93 | elif args.nexrad_archive:
94 | radar = pyart.io.read_nexrad_archive(file)
95 | elif args.nexrad_cdm:
96 | radar = pyart.io.read_nexrad_cdm(file)
97 | else:
98 | radar = pyart.io.read(file)
99 |
100 | # Call the bias calculation routine
101 | zdr_bias = zdrcal(radar, remove_first_n_gates=GATESKIP, sample_min=MINSAMPLE,\
102 | rhv_min=MINRHV, sig_min=MINSIG, dbz_min=MINDBZ, Htmax=MAXHT)
103 |
104 | # If minimun number of samples is met
105 | if zdr_bias is not None:
106 |
107 | txVar = ("Avg Zdr: "+str(zdr_bias['volume_average'])+\
108 | " Std Dev: "+str(zdr_bias['volume_standard_deviation'])+\
109 | " Good Pts: "+str(zdr_bias['volume_number_good'])+\
110 | " File: "+os.path.basename(fName))
111 |
112 | if zdr_bias['volume_standard_deviation'] > MINSIG:
113 | Stats[index,0] = np.ma.masked
114 | Stats[index,1] = np.ma.masked
115 | Stats[index,2] = np.ma.masked
116 | else:
117 | Stats[index,0] = zdr_bias['volume_average']
118 | Stats[index,1] = zdr_bias['volume_standard_deviation']
119 | Stats[index,2] = zdr_bias['volume_number_good']
120 |
121 | # Create plot if desired
122 | if args.plot:
123 | if index == 0:
124 | fig, ax, axes = plot_zdr_rhv(zdr_bias['height_average'],\
125 | zdr_bias['rhv_height_average'],\
126 | zdr_bias['range']['data']/1000.,\
127 | ymax=MAXHT,\
128 | xlims1=DR_LIMS, xlims2=RH_LIMS,\
129 | label=True, labelTx=zdr_bias['radar_name'])
130 | else:
131 | ## if zdr_bias['volume_standard_deviation'] > MINSIG:
132 | if zdr_bias['height_standard_deviation'].mean() > MINSIG:
133 | alff = 0.1
134 | else:
135 | alff = 1.
136 | oplot_zdr_rhv(zdr_bias['height_average'],\
137 | zdr_bias['rhv_height_average'],\
138 | zdr_bias['range']['data']/1000., \
139 | ax=axes, alf=alff)
140 | # Increment indices
141 | index += 1
142 | ## if zdr_bias['volume_standard_deviation'] <= MINSIG:
143 | if zdr_bias['height_standard_deviation'].mean() <= MINSIG:
144 | indexGood += 1
145 |
146 | RadName = zdr_bias['radar_name']
147 |
148 | del zdr_bias
149 | # If zdr_bias returns None, then too few samples were uncovered.
150 | else:
151 | txVar = ("Not enough samples in volume")
152 | RadName = "No_Data"
153 |
154 | print txVar
155 | OutTx.write(txVar+"\n")
156 |
157 | OutTx.write("ALL FILES WITH STD DEV < "+str(MINSIG)+" YIELD ("+str(indexGood)+" files)::\n")
158 | txVar = ("Avg Zdr: "+str(Stats[:,0].mean())+\
159 | " Std Dev: "+str(Stats[:,1].mean())+\
160 | " Good Pts: "+str(Stats[:,2].sum()))
161 | OutTx.write(txVar+"\n")
162 | OutTx.close()
163 |
164 | # Change the output name of the file
165 | cmd = 'mv zdr_offset.out '+RadName+'_zdr_offset.out'
166 | os.system(cmd)
167 |
168 | if multiStat:
169 | plot_add_stats(instance='multi', avg=Stats[:,0].mean(),\
170 | sd=Stats[:,1].mean(), points=Stats[:,2].sum(),\
171 | good_samples=indexGood, tot_samples=index)
172 | else:
173 | plot_add_stats(instance='single', avg=Stats[0,0],\
174 | sd=Stats[0,1], points=Stats[0,2])
175 |
176 | # plt.show()
177 | # Create final output file name
178 | fOut = RadName+'_'+pOut+'.'+pType
179 | print "Creating "+ fOut
180 | plt.savefig(fOut,format=pType)
181 |
--------------------------------------------------------------------------------
/scripts/listfile_raw.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 | """
3 | An exectutable program that outputs characteristics of raw Sigmet format
4 | files of radar data. An optional output file may be created.
5 |
6 | EXAMPLE USAGE::
7 | To create a list echoed to terminal:
8 | listfile_raw.py "/Users/nickguy/data/iphex/noxp/140615/raw/NOX*.RAW*" dump
9 |
10 | To create a list echoed to terminal and output file
11 | listfile_raw.py -m "/Users/nickguy/data/iphex/noxp/140615/raw/NOX*.RAW*" info_140615.txt
12 |
13 | HISTORY::
14 | 19 Jun 2014 Nick Guy (NRC; NOAA/NSSL)
15 |
16 | """
17 | #======================================================================
18 | # Load the needed packages
19 | import pyart.io as fio
20 | import argparse
21 | import os
22 | from glob import glob
23 | #======================================================================
24 |
25 | if __name__ == '__main__':
26 |
27 | # parse the arguments
28 | parser = argparse.ArgumentParser(
29 | description='Show file characteristics of raw Sigmet files.')
30 | parser.add_argument('searchstring', type=str, help='radar file(s) to list, if more than one file quotations are needed')
31 | parser.add_argument('outFile',type=str, help='output file name to store information')
32 |
33 | igroup = parser.add_argument_group(
34 | title='output characteristic file, optional',
35 | description=('Decide whether to create output file:'))
36 |
37 | parser.add_argument('-m', '--make_file', action='store_true',
38 | help='create an output text file with characteristics')
39 |
40 | args = parser.parse_args()
41 | # ======================================================================
42 |
43 | # Search for the file(s)
44 | flist = glob(args.searchstring)
45 |
46 | # Create an output file if requested
47 | if args.make_file:
48 | OutTx = open(args.outFile,'w')
49 |
50 | for file in flist:
51 | fLName, fileExt = os.path.splitext(file)
52 | fName = os.path.basename(file)
53 |
54 | # Get the file size
55 | fSize = os.path.getsize(file)
56 |
57 | # read in the file
58 | radar = fio.read_sigmet(file,file_field_names=True)
59 |
60 | # Create a latitude string
61 | if radar.latitude['units'] == 'degrees_north':
62 | rlat = str(radar.latitude['data'])[1:-1]+'N'
63 | if radar.latitude['units'] == 'degrees_south':
64 | rlat = str(radar.latitude['data'])[1:-1]+'S'
65 |
66 | # Create a longitude string
67 | if radar.longitude['units'] == 'degrees_east':
68 | rlon = str(radar.longitude['data'])[1:-1]+'E'
69 | if radar.longitude['units'] == 'degrees_west':
70 | rlon = str(radar.longitude['data'])[1:-1]+'W'
71 |
72 | # Create field names, check for extended header
73 | if radar.metadata['sigmet_extended_header'] == 'true':
74 | Vars = "'Xhdr', " + str(radar.fields.keys())[1:-1]
75 | else:
76 | Vars = str(radar.fields.keys())[1:-1]
77 |
78 | # Create Altitude variable
79 | Alt = str(radar.altitude['data'])[1:-1] + ' ' + radar.altitude['units']
80 |
81 | # Create PRF variable
82 | PRF = str(1./radar.instrument_parameters['prt']['data'][0]) + ' Hz'
83 |
84 | # Create Pulse width variable
85 | pWid = str(radar.instrument_parameters['pulse_width']['data'][0]) + ' us'
86 |
87 | txVar = (fName+" "+radar.metadata['instrument_name'] + " " +
88 | radar.metadata['sigmet_task_name'] + " File Size: " + str(fSize) +
89 | " Alt: " + Alt + " PRF: " + PRF +
90 | " Pulse Width: " + pWid + " " + Vars + " " + rlat + " " + rlon)
91 |
92 | # Echo this data to the terminal window
93 | print txVar
94 |
95 | # Add to output file if desired
96 | if args.make_file:
97 | OutTx.write(txVar+"\n")
98 |
99 | del fName, fileExt, radar, rlat, rlon, Vars, Alt, PRF, pWid, txVar
100 |
101 |
102 | if args.make_file:
103 | OutTx.close()
104 |
--------------------------------------------------------------------------------
/scripts/zdrcal_3panel:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 |
3 | #======================================================================
4 | # Import needed libraries
5 | import pyart
6 | import argparse
7 | import numpy as np
8 | import os
9 | import matplotlib.pylab as plt
10 | from glob import glob
11 |
12 | from pyradarmet.zdrcal import calculate_zdr_offset as zdrcal
13 | from pyradarmet.zdrcal import plot_zdr_rhv,plot_add_stats,oplot_zdr_rhv,plot_3panel,oplot_3panel
14 | #======================================================================
15 | # The user can modify these next variables to control how the program processes data
16 | MINSIG = 0.5 # Maximum standard deviation to accept in analysis
17 | MINSAMPLE = 100 # Minimum number of samples used to perform calculations
18 | MINDBZ = 5. # Minimum reflectivity to accept in analysis (proxy for SNR)
19 | GATESKIP = 7 # The number of gates at the beginning or array (closest to radar) to cut out
20 | MINRHV = 0.85 # Minimum correlation coefficient to accept in analysis
21 | MAXHT = 5. # Maximum height to accept in analysis
22 | ZH_LIMS = [-20., 30.] # Range of Reflectivity to plot
23 | RH_LIMS = [0.8, 1.0] # Range of Copolar Correlation Coefficient to plot
24 | DR_LIMS = [-1.5, 1.5] # Range of Differential reflectivity to plot
25 | KD_LIMS = [0.2, 0.4] # Range of Specific Differential Phase to plot
26 | PD_LIMS = [-.4,.4]
27 | STD_LIMS = [.1,.7]
28 |
29 | pOut = "zdr_bias_3panel"
30 | pType = "png"
31 | #======================================================================
32 |
33 | if __name__ == '__main__':
34 |
35 | # parse the arguments
36 | parser = argparse.ArgumentParser(
37 | description='Print differential reflectivity calibration statistics.')
38 | parser.add_argument('searchstring', type=str, help='radar file(s) for calculation, if more than one file quotations are needed')
39 |
40 | # group = parser.add_mutually_exclusive_group()
41 |
42 | igroup = parser.add_argument_group(
43 | title='ingest method, optional',
44 | description=('The method of file ingest can be specified. '
45 | 'If no ingest is specified, the format of the file will '
46 | 'be used to determine the best ingest method. '
47 | 'Specify only one of the following:'))
48 |
49 | igroup.add_argument('--sigmet', action='store_true',
50 | help='Sigmet/IRIS ingest')
51 | igroup.add_argument('--mdv', action='store_true', help='MDV ingest')
52 | igroup.add_argument('--cfradial', action='store_true',
53 | help='CF/Radial ingest')
54 | igroup.add_argument('--rsl', action='store_true',
55 | help='RSL ingest')
56 | igroup.add_argument('--nexrad_archive', action='store_true',
57 | help='NEXRAD level 2 archive ingest')
58 | igroup.add_argument('--nexrad_cdm', action='store_true',
59 | help='NEXRAD level 2 CDM ingest')
60 |
61 | parser.add_argument('-v', '--version', action='version',
62 | version='Py-ART version %s' % (pyart.__version__))
63 |
64 | parser.add_argument('-p', '--plot', action='store_true',
65 | help='create an output plot file')
66 | # parser.add_argument('-t', '--text', action='store_true',
67 | # help='create and output text file')
68 |
69 | args = parser.parse_args()
70 | # ======================================================================
71 |
72 | # Search for the file(s)
73 | # if args.dir:
74 | # os.chdir(args.dir)
75 | flist = glob(args.searchstring)
76 |
77 | multiStat = False
78 | if len(flist) > 1:
79 | multiStat = True
80 | # Create an array to hold data, only really used if multiple files
81 | Stats = np.ma.empty([len(flist),3])
82 |
83 | index = 0
84 | indexGood = 0
85 | for file in flist:
86 | fName, fileExt = os.path.splitext(file)
87 | # read in the file
88 | if args.sigmet:
89 | radar = pyart.io.read_sigmet(file)
90 | elif args.mdv:
91 | radar = pyart.io.read_mdv(file)
92 | elif args.cfradial:
93 | radar = pyart.io.read_cfradial(file)
94 | elif args.rsl:
95 | radar = pyart.io.read_rsl(file)
96 | elif args.nexrad_archive:
97 | radar = pyart.io.read_nexrad_archive(file)
98 | elif args.nexrad_cdm:
99 | radar = pyart.io.read_nexrad_cdm(file)
100 | else:
101 | radar = pyart.io.read(file)
102 |
103 | # Call the bias calculation routine
104 | zdr_bias = zdrcal(radar, remove_first_n_gates=GATESKIP, sample_min=MINSAMPLE,\
105 | rhv_min=MINRHV, sig_min=MINSIG, dbz_min=MINDBZ, Htmax=MAXHT)
106 |
107 | # If minimun number of samples is met
108 | if zdr_bias is not None:
109 | txVar = ("Avg Zdr: "+str(zdr_bias['volume_average'])+\
110 | " Std Dev: "+str(zdr_bias['volume_standard_deviation'])+\
111 | " Good Pts: "+str(zdr_bias['volume_number_good'])+\
112 | " File: "+os.path.basename(fName))
113 |
114 | if zdr_bias['volume_standard_deviation'] > MINSIG:
115 | Stats[index,0] = np.ma.masked
116 | Stats[index,1] = np.ma.masked
117 | Stats[index,2] = np.ma.masked
118 | else:
119 | Stats[index,0] = zdr_bias['volume_average']
120 | Stats[index,1] = zdr_bias['volume_standard_deviation']
121 | Stats[index,2] = zdr_bias['volume_number_good']
122 |
123 | # Create plot if desired
124 | if args.plot:
125 | if index == 0:
126 | ## fig, ax1, ax2, ax3 = plot_3panel(zdr_bias['dbz_height_average'],
127 | #### zdr_bias['height_average'],zdr_bias['kdp_height_average'],
128 | ## zdr_bias['height_average'],zdr_bias['height_standard_deviation'],
129 | ## zdr_bias['range']['data']/1000.,ymax=MAXHT,
130 | fig, ax1, ax2, ax3 = plot_3panel(zdr_bias['dbz_height_average_filt'],
131 | ## zdr_bias['height_average_filt'],zdr_bias['phidp_height_average_filt'],
132 | zdr_bias['height_average_filt'],zdr_bias['height_standard_deviation_filt'],
133 | zdr_bias['range']['data']/1000.,ymax=MAXHT,
134 | xlims1=ZH_LIMS,xlims2=DR_LIMS,xlims3=std_lims,
135 | label=True,labelTx=zdr_bias['radar_name'],X3lab='Std Dev')
136 | else:
137 | if zdr_bias['height_standard_deviation'].mean() > MINSIG:
138 | alff = 0.1
139 | else:
140 | alff = 1.
141 | oplot_3panel(zdr_bias['dbz_height_average'],zdr_bias['height_average'],
142 | zdr_bias['height_standard_deviation'],zdr_bias['range']['data']/1000.,
143 | ax1=ax1,ax2=ax2,ax3=ax3,alf=alff)
144 | # Increment indices
145 | index += 1
146 | if zdr_bias['height_standard_deviation'].mean() > MINSIG:
147 | indexGood += 1
148 |
149 | RadName = zdr_bias['radar_name']
150 |
151 | del zdr_bias
152 | # If zdr_bias returns None, then too few samples were uncovered.
153 | else:
154 | txVar = ("Not enough samples in volume")
155 | print txVar
156 |
157 | if multiStat:
158 | plot_add_stats(instance='multi',avg=Stats[:,0].mean(),
159 | sd=Stats[:,1].mean(),points=Stats[:,2].sum(),
160 | good_samples=indexGood,tot_samples=index,labelpos=(.4,.83))
161 | else:
162 | plot_add_stats(instance='single',avg=Stats[0,0],sd=Stats[0,1],points=Stats[0,2],
163 | labelpos=(.4,.83))
164 |
165 | # plt.show()
166 | # Create final output file name
167 | fOut = RadName+'_'+pOut+'.'+pType
168 | print "Creating "+ fOut
169 | plt.savefig(fOut,format=pType)
170 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """PyRadarMet: Python Fundamental Calculations in Radar Meteorology
2 |
3 | PyRadarMet is a toolkit that contains a variety of utilities that can be used
4 | to calculate fundamental radar meteorology values:
5 |
6 | * Convert between linear reflectivity(Z) and log reflectivity (dBZ)
7 | * Calculate coefficients for attenuation calculations
8 | * Calculate fundamental radar system characteristics
9 | * Calculate Doppler radar characteristics
10 | * Calculate geometrical characterisistics of radar
11 | * Calculate variables from radar output
12 | * Calculate ZDR bias of radar system
13 |
14 | """
15 |
16 | import os
17 | import sys
18 | import urllib2
19 | import shutil
20 | #from shutil import copyfile
21 | import tarfile
22 | import time
23 | from setuptools import setup
24 |
25 | #- Pull the header into a variable
26 | doclines = __doc__.split("\n")
27 |
28 | VERSION = '0.1.3'
29 |
30 | #- Set variables for setup
31 | PACKNAME = 'pyradarmet'
32 | PACKAGES = [PACKNAME]
33 | URL = 'https://github.com/nguy/PyRadarMet'
34 | AUTHOR = 'Nick Guy'
35 | EMAIL = "nick.guy@uwyo.edu"
36 | LICENSE = 'LICENSE.txt'
37 |
38 | #DEMDIR = PACKNAME + os.sep.join([os.path.dirname(__file__), 'data'])
39 |
40 | package_dirs={'':PACKNAME}
41 | #datafiles = glob.glob(os.path.join(pathout,'*'))
42 | #datafiles = [os.path.join('data',os.path.basename(f)) for f in datafiles]
43 | #package_data = {'pyradarmet':datafiles}
44 |
45 | # Check for Pyart package - Avoid conflict with PyPI
46 |
47 | try:
48 | import pyart
49 | except ImportError as e:
50 | print("Could not find PyArt Install\n\
51 | Please go to https://github.com/ARM-DOE/pyart for PyArt install")
52 | sys.exit()
53 |
54 | # Read the text file with location of where to install
55 | #demfiletxt = open('pyradarmet/demfile_locator.txt', 'r')
56 | #DEMInstall = demfiletxt.readline()
57 | #demfiletxt.close()
58 |
59 | # Check for trailing directory "/"
60 | #if DEMInstall[-1] is not '/':
61 | # DEMInstall = DEMInstall + '/'
62 | ########
63 |
64 | gtopourl="ftp://edcftp.cr.usgs.gov/data/gtopo30/global/"
65 |
66 | try:
67 | basefolder = os.environ["GTOPO_DATA"]
68 | print "Topo data will be installed to %s" % os.environ["GTOPO_DATA"]
69 | except KeyError:
70 | basefolder = "./pyradarmet/"
71 | print "GTOPO_DATA environmental variable not set, "\
72 | "using default path in package directory"
73 |
74 | archfolder=os.path.join(basefolder, 'gz')
75 | datafolder=os.path.join(basefolder, 'data')
76 |
77 | names =["antarcps", "e060n40", "e100n40", "e140n40", "w020n40", "w060n90",
78 | "w100n90", "w140n90", "w180s10", "e020n40", "e060n90", "e100n90",
79 | "e140n90", "w020n90", "w060s10", "w100s10", "w140s10", "w180s60",
80 | "e020n90", "e060s10", "e100s10", "e140s10", "w020s10", "w060s60",
81 | "w120s60", "w180n40", "e020s10", "e060s60", "e120s60", "w000s60",
82 | "w060n40", "w100n40", "w140n40", "w180n90"]
83 |
84 | for folder in [archfolder, datafolder]:
85 | print folder
86 | if not os.path.exists(folder):
87 | os.makedirs(folder)
88 |
89 | start_time = time.time()
90 |
91 | for filename in names:
92 | archfilebase = filename+".tar.gz"
93 | archfile = os.path.join(archfolder, archfilebase)
94 | hdrfilebase = filename+".hdr"
95 | demfilebase = filename+".dem"
96 |
97 | if not os.path.exists(archfile):
98 | print("downloading: {0}".format(archfilebase))
99 | ftpfile = urllib2.urlopen(gtopourl+archfilebase)
100 | localfile = open(archfile, "wb")
101 | shutil.copyfileobj(ftpfile, localfile)
102 | localfile.close()
103 |
104 | for exfilebase in [hdrfilebase, demfilebase]:
105 | exfile = os.path.join(datafolder,exfilebase)
106 | if not os.path.exists(exfile):
107 | print("extracting: {0} from {1}".format(exfilebase, archfilebase))
108 | tar = tarfile.open(archfile)
109 | tarobj = tar.extractfile(exfilebase.upper())
110 | localfile = open(exfile, "wb")
111 | shutil.copyfileobj(tarobj, localfile)
112 | tarobj.close()
113 | localfile.close()
114 | tar.close()
115 |
116 | download_time = time.time() - start_time
117 | print "Total download time: %g seconds"%(download_time)
118 | #print "Removing tar directory: %s" % archfolder
119 | #shutil.rmtree(archfolder)
120 | #########
121 | #- Run setup
122 | if datafolder == './pyradarmet/data/':
123 | setup(
124 | name=PACKNAME,
125 | version=VERSION,
126 | url=URL,
127 | author=AUTHOR,
128 | author_email=EMAIL,
129 | description=doclines[0],
130 | license=LICENSE,
131 | packages=PACKAGES,
132 | package_data={PACKNAME: [datafolder+'*']},
133 | include_package_data=True,
134 | classifiers=["""
135 | Development Status :: 3 - Alpha,
136 | Programming Language :: Python",
137 | Topic :: Scientific/Engineering
138 | Topic :: Scientific/Engineering :: Atmospheric Science
139 | Operating System :: Unix
140 | Operating System :: POSIX :: Linux
141 | Operating System :: MacOS
142 | """],
143 | long_description = """
144 | A toolkit that contains a variety of utilities that can be used
145 | to calculate fundamental radar meteorology values:
146 |
147 | * Convert between linear reflectivity(Z) and log reflectivity (dBZ)
148 | * Calculate coefficients for attenuation calculations
149 | * Calculate fundamental radar system characteristics
150 | * Calculate Doppler radar characteristics
151 | * Calculate geometrical characterisistics of radar
152 | * Calculate variables from radar output
153 | * Calculate ZDR bias of radar system""",
154 | install_requires = ['Numpy >=1.7.2'],
155 | )
156 | else:
157 | print "In EXTERNAL"
158 |
159 | setup(
160 | name=PACKNAME,
161 | version=VERSION,
162 | url=URL,
163 | author=AUTHOR,
164 | author_email=EMAIL,
165 | description=doclines[0],
166 | license=LICENSE,
167 | packages=PACKAGES,
168 | classifiers=["""
169 | Development Status :: 3 - Alpha,
170 | Programming Language :: Python",
171 | Topic :: Scientific/Engineering
172 | Topic :: Scientific/Engineering :: Atmospheric Science
173 | Operating System :: Unix
174 | Operating System :: POSIX :: Linux
175 | Operating System :: MacOS
176 | """],
177 | long_description = """
178 | A toolkit that contains a variety of utilities that can be used
179 | to calculate fundamental radar meteorology values:
180 |
181 | * Convert between linear reflectivity(Z) and log reflectivity (dBZ)
182 | * Calculate coefficients for attenuation calculations
183 | * Calculate fundamental radar system characteristics
184 | * Calculate Doppler radar characteristics
185 | * Calculate geometrical characterisistics of radar
186 | * Calculate variables from radar output
187 | * Calculate ZDR bias of radar system""",
188 | install_requires = ['Numpy >=1.7.2'],
189 | )
190 |
191 |
192 | install_time = time.time() - start_time
193 | print "Total install time: %g seconds"%(install_time)
194 |
--------------------------------------------------------------------------------