├── 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 | --------------------------------------------------------------------------------