diff --git a/.gitignore b/.gitignore index 085d018a8..ba63b60f6 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,7 @@ site/ *.iqdat *.hdf5 hdw.dat* + +# mac +**/.DS_Store + diff --git a/.zenodo.json b/.zenodo.json index ac8fc2793..2917f6a4a 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -3,11 +3,6 @@ { "name": "SuperDARN Data Visualization Working Group" }, - { - "affiliation": "University of Saskatchewan", - "name": "Schmidt, M.T.", - "orcid": "0000-0002-3265-977X" - }, { "affiliation": "University of Saskatchewan", "name": "Martin, C.J.", @@ -18,42 +13,62 @@ "name": "Shi, X.", "orcid": "0000-0001-8425-8241" }, - { "affiliation": "University of Scranton", - "name": "Tholley, F." - }, { "affiliation": "University of Saskatchewan", - "name": "Billett, D.D.", - "orcid": "0000-0002-8905-8609" + "name": "Schmidt, M.T.", + "orcid": "0000-0002-3265-977X" + }, + { + "affiliation": "Lancaster University", + "name": "Day, E. K.", + "orcid": "0000-0002-0169-6201" }, { "affiliation": "The University Centre in Svalbard", "name": "Bland, E.C.", "orcid": "0000-0002-0252-0400" }, + { + "affiliation": "University of Alabama", + "name": "Khanal, K.", + "orcid": "0000-0003-3927-7501" + }, + { + "affiliation": "University of Saskatchewan", + "name": "Billett, D.D.", + "orcid": "0000-0002-8905-8609" + }, { "affiliation": "Virginia Tech", - "name": "Coyle, S.", - "orcid": "0000-0003-1730-2753" + "name": "Kunduri, B.S.R.", + "orcid": "0000-0002-7406-7641" + }, + { "affiliation": "University of Scranton", + "name": "Tholley, F." }, { "affiliation": "University of Scranton", "name": "Frissell, N.", "orcid": "0000-0002-8398-4222" }, + { + "affiliation": "Virginia Tech", + "name": "Coyle, S.", + "orcid": "0000-0003-1730-2753" + }, { "affiliation": "University of Saskatchewan", - "name": "Huyghebaert, D.", - "orcid": "0000-0002-4257-4235" + "name": "Rohel, R.A.", + "orcid": "0000-0003-2208-1553" }, { - "affiliation": "University of Alabama", - "name": "Khanal, K.", - "orcid": "0000-0003-3927-7501" + "affiliation": "University of Saskatchewan", + "name": "Kolkman, T.J.", + "orcid": "0000-0001-9759-9045" }, { - "affiliation": "University of Bergen", - "name": "Reistad, J.P.", - "orcid": "0000-0003-3509-5479" + "affiliation": "University of Saskatchewan", + "name": "Krieger, K.J.", + "orcid": "0000-0002-3678-4201" }] } diff --git a/README.md b/README.md index e62bde685..992bfdeb9 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,20 @@ Python data visualization library for the Super Dual Auroral Radar Network (Supe ## Changelog -## Version 3.0 - Release! +## Version 3.1 - Release! + +pyDARN release v3.1 includes the following features: +- Full Cartopy coastline plotting options for all spatial plots + - **NEW** `coastline` keyword in method calls +- Full Cartopy integration for plotting in geographic coordinates for grid and fan plots +- Completed polar coordinate convection maps including reference vector and many customization options +- Improved ACF plotting +- New `HALF_SLANT` range estimation for RTP +- **Bug fix** Multiple fan plots now available on one axis +- **Bug fix** `lowlat` keyword now available for geographic coordinate plots +- **Bug fix** Colorbars now extend/don't extend as required +along with many other minor improvements and bug fixes! -**New Requirement**: pyDARN 3.0 requires minimum matplotlib version of 3.3.4 - -pyDARN release v3.0 includes the following features: -- **New** optional cartopy dependency -- **New** convection map plotting ## Documentation diff --git a/docs/imgs/acf_plot1.png b/docs/imgs/acf_plot1.png index b185e34df..665c94191 100644 Binary files a/docs/imgs/acf_plot1.png and b/docs/imgs/acf_plot1.png differ diff --git a/docs/imgs/fov_8.png b/docs/imgs/fov_8.png new file mode 100644 index 000000000..7f2bdd53d Binary files /dev/null and b/docs/imgs/fov_8.png differ diff --git a/docs/imgs/subplots.png b/docs/imgs/subplots.png new file mode 100644 index 000000000..03cdd5c3f Binary files /dev/null and b/docs/imgs/subplots.png differ diff --git a/docs/index.md b/docs/index.md index 296552eaa..d2b23c122 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,11 +17,11 @@ If you have any questions or concerns please submit an **Issue** on the SuperDAR - Tutorials - [Reading a SuperDARN File](user/io.md) - [Radar and Hardware Information](user/hardware.md) - - [Coordinate Systems](user/coordinates.md) + - [Axes Setup](user/axis.md) + - [Ranges, Coords and Projs](user/coordinates.md) - [Range-Time plots](user/range_time.md) - [Time-Series plots](user/time_series.md) - [Summary plots](user/summary.md) - - [Axis](user/axis.md) - [FOV plots](user/fov.md) - [Fan plots](user/fan.md) - [Grid plots](user/grid.md) diff --git a/docs/user/acf.md b/docs/user/acf.md index ddc5ed5ea..9c2e37fed 100644 --- a/docs/user/acf.md +++ b/docs/user/acf.md @@ -14,19 +14,19 @@ the additional permissions listed below. ### Auto-Correlation Function Plots -`plot_acfs` simply plots the Auto-Correlation Function (ACF) of the imaginary and real parts in the selected RAWACF file. +`plot_acfs` plots the imaginary and real parts of the Auto-Correlation Function (ACF), along with the power and phase of the ACF in the selected RAWACF file. Basic code to plot ACFs from a RAWACF file would look like: ```python -import matplotlib.pyplot as plt import pydarn +import matplotlib.pyplot as plt +from datetime import datetime - -file = "20180101.0000.01.rkn.rawacf" -sdarn_read = pydarn.SuperDARNRead(file) -rawacf_data = sdarn_read.read_rawacf() - -pydarn.ACF.plot_acfs(rawacf_data) +reader = pydarn.SuperDARNRead("data/20140105.1208.03.rkn.rawacf") +rawacf_data = reader.read_rawacf() +plt.figure(figsize=(12, 7)) +pydarn.ACF.plot_acfs(rawacf_data, beam_num=7, gate_num=44, + start_time=datetime(2014, 1, 5, 12, 8)) plt.show() ``` @@ -37,19 +37,19 @@ You also have access to numerous plotting options: | Parameter | Action | | ---------------------- | ------------------------------------------------------------------------------- | -| beam_num=0 | beam number to plot | -| gate_num=0 | gate number to plot | -| parameter='acfd' | parameter to pick between acfd or xcfd plotting | -| scan_num=0 | the scan number to plot | +| beam_num=(int) | beam number to plot | +| gate_num=(int) | gate number to plot | +| parameter=(string) | parameter to pick between acfd or xcfd plotting | +| scan_num=(int) | the scan number to plot | | start_time=None | plot the closest beam scan to the given start time (overrides the scan number if set) | -| ax | matplotlib axes object | -| normalized=True | normalizes the parameter data with the associated power 0 value | -| real_color='red' | Real part of the parameter line color | -| imaginary_color='blue' | Imaginary part of the parameter line color | -| plot_blank=False | Determine if blanked lags should be plotted | -| blank_marker='o' | Choice of marker to indicate blanked lags are a dot (general python markers accepted) | -| legend=True | plot a legend | -| kwargs | arguments passed in matplotlib line_plot for real and imaginary plots | +| normalized=(bool) | normalizes the parameter data with the associated power 0 value | +| real_color=(str) | Real part of the parameter line color | +| imaginary_color=(str) | Imaginary part of the parameter line color | +| plot_blank=(bool) | Determine if blanked lags should be plotted | +| blank_marker=(str) | Choice of marker to indicate blanked lags are a dot (general python markers accepted) | +| legend=(bool) | plot a legend | +| pwr_and_phs=(bool) | Plots subplots of power and phase of the ACF | +| kwargs** | arguments passed in matplotlib line_plot for real and imaginary plots | If blank lags are present in the data, it will look similar to the following: @@ -61,7 +61,7 @@ from datetime import datetime rawacf_file = '20140105.1200.03.cly.rawacf' rawacf_data = pydarn.SuperDARNRead(rawacf_file).read_rawacf() -pydarn.ACF.plot_acfs(rawacf_data, beam_num=15, gate_num=16, start_time=datetime(2014, 1, 5, 13, 30)) +pydarn.ACF.plot_acfs(rawacf_data, beam_num=15, gate_num=16, start_time=datetime(2014, 1, 5, 13, 30), pwr_and_phs=False) plt.show() ``` diff --git a/docs/user/axis.md b/docs/user/axis.md index 69dfdb2d5..fd176d0fb 100644 --- a/docs/user/axis.md +++ b/docs/user/axis.md @@ -1,12 +1,72 @@ -# Axis +# Axes Setup -For plots using geographical coordinates, a wide selection of axis representation is possible. Currently pyDARN only offers polar coordinate axis for fan, fov, and grid plots. +For spatial plots (FOV, Fan, Grid), pyDARN allows users to choose between Polar and +Geographic axes using the `projs` keyword and `Projs` module. +Convection maps do not allow for geographic projects due to lack of interest. -## Polar +## Projs.POLAR | Option | Action | | ---------------------------- | ----------------------------------------------------------------- | | lowlat=(int) | Lower Latitude boundary for the polar plot (degree) (default: 30) | -| hemisphere=(pydarn.Hemisphere) | Hemisphere of the radar (default: Hemisphere.North) | +| hemisphere=(pydarn.Hemisphere) | Hemisphere of the radar (default: Hemisphere.North) | +| coastline=(bool) | Uses Cartopy to add outlines fo the coastlines below data | +This choice will return an `ax` object and a value of None for `ccrs`. + +## Projs.GEO + +**REQUIRES CARTOPY INSTALLATION** + +| Option | Action | +| ---------------------------- | ----------------------------------------------------------------- | +| lowlat=(int) | Lower Latitude boundary for the polar plot (degree) (default: 30) | +| hemisphere=(pydarn.Hemisphere) | Hemisphere of the radar (default: Hemisphere.North) | +| coastline=(bool) | Uses Cartopy to add outlines fo the coastlines below data | +| grid_lines=(bool) | Uses Cartopy to plot grid lines | + +This choice will return an `ax` object and a Cartopy `ccrs` object. + +## Custom Axes +pyDARN does not currently support use of custom axes to read in and plot on. This means +that use of subplots is not supported. There are ways to get around this if a custom axis +that has the same setup as either axes above is read into the subplot. For example, a polar +and a geographic plots can be positioned using subplots as follows: + +```python +import pydarn +import datetime as dt +import numpy as np +import matplotlib.pyplot as plt +import cartopy.crs as ccrs + +# Polar plot +date=dt.datetime(2022, 1, 8, 14, 5) +fig = plt.figure(figsize=(6, 6)) +ax1 = fig.add_subplot(121, projection='polar') +ax1.set_ylim(90, 30) +ax1.set_yticks(np.arange(30, 90, 10)) +ax1.set_xticks([0, np.radians(45), np.radians(90), np.radians(135), + np.radians(180), np.radians(225), np.radians(270), + np.radians(315)]) +ax1.set_xticklabels(['00', '', '06', '', '12', '', '18', '']) +ax1.set_theta_zero_location("S") +pydarn.Fan.plot_fov(stid=65, date=date, ax=ax1) + +# Geo plot +deg_from_midnight = (date.hour + date.minute / 60) / 24 * 360 +pole_lat = 90 +noon = -deg_from_midnight +ylocations = -5 +proj = ccrs.Orthographic(noon, pole_lat) +ax2 = fig.add_subplot(122, projection=proj, aspect='auto') +ax2.gridlines(draw_labels=True) +extent = min(45e5,(abs(proj.transform_point(noon, 30, ccrs.PlateCarree())[1]))) +ax2.set_extent(extents=(-extent, extent, -extent, extent), crs=proj) +pydarn.Fan.plot_fov(stid=65, date=date, ax=ax2, ccrs=ccrs, coords=pydarn.Coords.GEOGRAPHIC, projs=pydarn.Projs.GEO) +plt.tight_layout() +plt.show() +``` + +![](../imgs/subplots.png) diff --git a/docs/user/coordinates.md b/docs/user/coordinates.md index 737075e19..9bc73a187 100644 --- a/docs/user/coordinates.md +++ b/docs/user/coordinates.md @@ -12,18 +12,23 @@ and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. --> -# Coordinate Systems +# Ranges, Coords and Projs --- -pyDARN offers several different measurement systems for various types of plotting: +pyDARN uses several different measurement and plotting systems to easily allow the user to customise their plots, this page aims to describe their uses: - Range Estimation: the estimate of how far the target (echo) is from the radar -- Coordinate systems: Generic geographic coordinate system with conversions to AACGM and AACGM MLT +- Coordinate systems: determines the unique position using a set of points - primarily we use geographic and magnetic coordinate systems for Earth +- Projections: used primarily on spatial plots (FOV, Fan, Grid...) projection choices allow the user to choose what type of projection the plot appears in -## Range-Time Plots +## RangeEstimation **Range Gates**: `RangeEstimation.RANGE_GATE` a rectangle section determined by beam width and set distance for each range (nominally 45 km). RAWACF and FITACF data give their parameter values with respect to range gates. Range gates are a unit-less measure of estimating distance. -**Slant Range**: `RangeEstimation.SLANT_RANGE` is a conversion from range gates to km units. Slant range estimates the distance of ionospheric echos from the radar, using the time it takes for the radio wave to travel to the ionosphere and return, assuming the radio wave is travelling at the speed of light. +**Slant Range**: `RangeEstimation.SLANT_RANGE` is a conversion from range gates to km units. Slant range estimates the distance of ionospheric echos from the radar, using the time it takes for the radio wave to travel to the ionosphere and return, assuming the radio wave is travelling at the speed of light. Measured in km. + +**Half Slant**: `RangeEstimation.HALF_SLANT` is slant range divided by two, measured in km. + +**Ground Scatter Mapped Range**: `RangeEstimation.GSMR` uses echos from ground scatter to adjust slant range coordinates to be more accurate based on [Dr. Bill Bristow's paper](https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/93JA01470). Implemented by Dr. Nathaniel Frissell and Francis Tholley from University of Scranton. Measured in km. !!! Note Slant range is calculated from the value of `frang`, the distance to the first range gate. In pyDARN, we assume @@ -33,25 +38,37 @@ pyDARN offers several different measurement systems for various types of plottin due to discussion *outside of pyDARN's remit* the value of `rxrise` is adjusted in FITACF files and may not match the value given in hardware files. Currently, pyDARN has decided to use the values for `rxrise` given in the hardware files. We will amend or reconsider this approach as and when a solution to the differing values is found. + In some plots, the user can change the `frang` value to fit their needs. -**Ground Scatter Mapped Range**: `RangeEstimation.GSMR` uses echos from ground scatter to adjust slant-range coordinates to be more accurate based on [Dr. Bill Bristow's paper](https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/93JA01470). Implemented by Dr. Nathaniel Frissell and Francis Tholley from University of Scranton. Measured in km. +## Coords -**Geographic Latitude** (Coming Soon) +Used to determine the position of data in spatial plots: fan, grid and convection map plots +Range time plots now allow for `Coords` use. The y-axis can be converted to latitude, longitude or MLT using a the `coords` keyword. +E.G. using `coords=Coords.Geographic` and `lat_or_lon='lon'` in the method call will convert the chosen range estimate (see above) into Geographic Longitudes. -**AACGM Latitude** (Coming Soon) +**Geographic**: `Coords.GEOGRAPHIC` is the standard geographical coordinate system for latitude and longitude (degrees) -## Geographic Plots +**AACGM**: `Coords.AACGM` is [Altitude Adjusted Corrected Geogmagnetic Coordinates developed by Dr. Simon Shepherd](http://superdarn.thayer.dartmouth.edu/aacgm.html) are an extension of corrected geomagnetic (CGM) coordinates that more accurately represent the actual magnetic field. In AACGM coordinates points along a given magnetic field line are given the same coordinates and thus are a better reflection of magnetic conjugacy. pyDARN uses AACGM-V2 from the [aacgmv2 python library](https://pypi.org/project/aacgmv2/). -Geographic plots include: fan, grid and convection map (coming soon) plots +**AACGM_MLT**: `Coords.AACGM_MLT` is `Coords.AACGM` with the geomagnetic longitude converted to magnetic local time. -**Geographic**: `Coords.GEOGRAPHIC` is the standard geographical coordinate system for latitude and longitude (degree) (coming soon) +`RangeEstimation` methods can be used with a `Coords` calculation. For example, using `Coords.GEOGRAPHIC` and `RangeEstimation.GSMR` together, will give a plot of ionospheric echoes at a distance from the radar calculated in ground scatter mapped range, in geographic coordinates. -**AACGM**: `Coords.AACGM` is [Altitude Adjusted Corrected Geogmagnetic Coordinates developed by Dr. Simon Shepherd](http://superdarn.thayer.dartmouth.edu/aacgm.html) are an extension of corrected geomagnetic (CGM) coordinates that more accurately represent the actual magnetic field. In AACGM coordinates points along a given magnetic field line are given the same coordinates and thus are a better reflection of magnetic conjugacy. pyDARN uses AACGM-V2 from the [aacgmv2 python library](https://pypi.org/project/aacgmv2/). Implemented by Dr. Daniel Billett from University of Saskatchewan. +## Projs -**AACGM_MLT**: `Coords.AACGM_MLT` is `Coords.AACGM` with the geomagnetic longitude converted to magnetic local time +Spatial plots have two options for projections. -`RangeEstimation` methods can be used with a `Coords` calculation. For example, using `Coords.GEOGRAPHIC` and `RangeEstimation.GSMR` together, will give a plot of ionospheric echoes at a distance from the radar calculated in ground scatter mapped range, in geographic coordinates. +**Polar**: `Projs.POLAR` sets up the axis of the spatial plot in polar coordinates common in studies that show data over the poles. -!!! Warning - You cannot use `RangeEstimation.RANGE_GATES` with any `Coords`, the default is `RangeEstimation.SLANT_RANGE` +**Geographic**: `Projs.GEO` sets up the axes of the spatial plots in geographic coordinates using Cartopy. +!!! Note + The 'look-direction' of the two projections are different for the Southern hemisphere. + Polar projections show a view of the south pole as if looking down through the planet from above the north pole, + geographic projections show a view of the south pole as if looking from above the south pole. + +!!! Note + Some combinations of Projs/Coords/RangeEstimates are not designed to work. + For example, you cannot plot a fan plot using range gates, spatial plots require a value in kilometers. + At the moment, AACGM Coordinates do not plot on Geographic projections as it has not been developed yet. + Convection maps only support polar projections due to lack of interest in requiring geographic projections. \ No newline at end of file diff --git a/docs/user/fan.md b/docs/user/fan.md index 03fe671f2..ab0149a26 100644 --- a/docs/user/fan.md +++ b/docs/user/fan.md @@ -2,6 +2,7 @@ Author(s): Daniel Billet Modifications: 20210922: CJM - included info on new channel option +20220912: CJM - Updated for new changes Disclaimer: pyDARN is under the LGPL v3 license found in the root directory LICENSE.md @@ -21,8 +22,7 @@ Fan plots are a way to visualise data from the entire scan of a SuperDARN radar. All beams and ranges for a given parameter (such as line-of-sight velocity, backscatter power, etc) and a particular scan can be projected onto a polar format plot in [AACGMv2](http://superdarn.thayer.dartmouth.edu/aacgm.html) coordinates, or projected onto a geographic plot in geographic coordinates. !!! Warning - At present, AACGM coordinates cannot be plotted on a geographic projection. - + AACGM coordinates cannot be plotted on a geographic projection. The mapping of the range gate corners was based on [rbpos in RST](https://github.com/SuperDARN/rst/blob/0aa1fffed4cc48c1eb6372dfc9effa688af95624/codebase/superdarn/src.idl/lib/legacy.1.6/rbposlib.pro) that is coded in `/pydarn/geo.py`. To get the coordinates read [hardware docs](hardware.md). @@ -31,6 +31,7 @@ pyDARN and pyplot need to be imported, as well as any FITACF file needs to be [r ```python import matplotlib.pyplot as plt +from datetime import datetime import pydarn #Read in fitACF file using SuperDARDRead, then read_fitacf @@ -50,8 +51,9 @@ In this example, the 27th scan was plotted with the defaulted parameter being li You can also provide a `datetime` object to obtain a scan at a specific time: ```python -pydarn.Fan.plot_fan(cly_data, scan_index=datetime(2015, 3, 8, 15, 26), +pydarn.Fan.plot_fan(fitacf_data, scan_index=datetime(2015, 3, 8, 15, 26), colorbar_label='Velocity [m/s]') +plt.show() ``` ![](../imgs/fan_1.b.png) @@ -72,8 +74,8 @@ plt.show() You might have noticed that the variable `fanplot` in the examples above actually holds some information. This contains the AACGM latitude and longitude of the fan just plotted, as well as the data, ground scatter information, and datetime object. If you instead change `fanplot` to 5 separate variables, it will return the latitude, longitude, data, groundscatter and datetime info into seperate variables: ```python -ax, lats, lons, data, grndsct, datetime = pydarn.Fan.plot_fan(fitacf_data, - scan_index=27) +ax, lats, lons, data, grndsct = pydarn.Fan.plot_fan(fitacf_data, + scan_index=27) lats.shape @@ -104,6 +106,7 @@ Here is a list of all the current options than can be used with `plot_fan` | groundscatter=(bool) | True or false to showing ground scatter as grey | | ranges=(list) | Two element list giving the lower and upper ranges to plot, grabs ranges from hardware file (default [] | | cmap=string | A matplotlib color map string | +| grid=(bool) | Boolean to apply the grid lay of the FOV (default: False ) | | zmin=(int) | Minimum data value for colouring | | zmax=(int) | Maximum data value for colouring | | colorbar=(bool) | Set true to plot a colorbar (default: True) | @@ -112,6 +115,8 @@ Here is a list of all the current options than can be used with `plot_fan` | boundary=(bool) | Set false to not show the outline of the radar FOV (default: True) | | coords=(Coords) | [Coordinates](coordinates.md) for the data to be plotted in | | projs=(Projs) | Projections to plot the data on top of | +| colorbar_label=(string) | Label that appears next to the color bar, requires colorbar to be True | +| coastline=(bool) | Plots outlines of coastlines below data (Uses Cartopy) | | kwargs ** | Axis Polar settings. See [polar axis](axis.md) | @@ -120,7 +125,7 @@ Here is a list of all the current options than can be used with `plot_fan` In other cases, the user may want to specify the channel and use an integer (N) for the `scan_index`. Be aware that this will show the data for the Nth scan of only the chosen channel, not that of the entire file. -`plot_fan` can concatenate with other plots including itself, here is an example of plotting two different radars with some of the above parameters: +`plot_fan` can concatenate with itself in both polar and geographic projection, here is an example of plotting two different radars with some of the above parameters, remember to pass in the axes object (`ax`) to any subsequent plots, unlike the FOV plots, fan plots will automatically control the `ccrs` variable for you: ```python import pydarn @@ -133,28 +138,28 @@ pyk_file = 'data/20150308.1401.00.pyk.fitacf' pyk_data = pydarn.SuperDARNRead().read_dmap(pyk_file) cly_data = pydarn.SuperDARNRead().read_dmap(cly_file) -pydarn.Fan.plot_fan(cly_data, scan_index=datetime(2015, 3, 8, 14, 4), +ax, _, _, _, _ = pydarn.Fan.plot_fan(cly_data, scan_index=datetime(2015, 3, 8, 14, 4), colorbar=False, fov_color='grey', line_color='blue', radar_label=True) pydarn.Fan.plot_fan(pyk_data, scan_index=datetime(2015, 3, 8, 14, 4), colorbar_label='Velocity [m/s]', fov_color='grey', - line_color='red', radar_label=True) + line_color='red', radar_label=True, ax=ax) -#pydarn.Fan.plot_fov(66, datetime(2015, 3, 8, 15, 0), radar_label=True) plt.show() ``` ![](../imgs/fan_3.png) -Using *cartopy* to plot underlaid coastline map: - -!!! Warning - The *cartopy* coastlines mapping feature is currently only available for geographic projections in geographic coordinates. Expansion of this feature is coming soon! +Using *cartopy* to plot underlaid coastline map using the `coastline` keyword, this example also shows the use of plotting in geographic coordinates: ```python -ax, _, _, _, _ = pydarn.Fan.plot_fan(data, scan_index=5, radar_label=True, groundscatter=True, coords=pydarn.Coords.GEOGRAPHIC, projs=pydarn. Projs.GEO, colorbar_label="Velocity m/s") -ax.coastlines() +ax, _, _, _, _ = pydarn.Fan.plot_fan(data, scan_index=5, radar_label=True, + groundscatter=True, + coords=pydarn.Coords.GEOGRAPHIC, + projs=pydarn.Projs.GEO, + colorbar_label="Velocity m/s", + coastline=True) plt.show() ``` diff --git a/docs/user/fov.md b/docs/user/fov.md index d27bab134..c17435d80 100644 --- a/docs/user/fov.md +++ b/docs/user/fov.md @@ -2,6 +2,7 @@ Author(s): Marina Schmidt Modifications: 2022-03-31 MTS updating documentation with the new coordinate/cartopy system +2022-09-12 CJM - Updated for new changes Disclaimer: pyDARN is under the LGPL v3 license found in the root directory LICENSE.md @@ -40,6 +41,7 @@ Here is a list of all the current options than can be used with `plot_fov` | stid=(int) | Station id of the radar. Can be found using [SuperDARNRadars](hardware.md) | | date=(datetime) | `datetime` object to determine the position the radar fov AACGM or AACGM MLT coordinates | | ranges=(list) | Two element list giving the lower and upper ranges to plot, grabs ranges from hardware file (default [] | +| ax=(Axes Object) | Matplotlib axes object than can be used for cartopy additions | | ccrs=(object) | Cartopy axes object for plotting using Cartopy | | rsep=(int) | Range Seperation (km) (default: 45 km) | | frang=(int) | Frequency Range (km) (default: 180 km) | @@ -56,22 +58,28 @@ Here is a list of all the current options than can be used with `plot_fov` | line_alpha=(int) | Sets the transparency of the boundary and grid lines if shown (default: 0.5) | | radar_location=(bool) | Places a dot in the plot representing the radar location (default: True) | | radar_label=(bool) | Places the radar 3-letter abbreviation next to the radar location | +| coastline=(bool) | Plots outlines of coastlines below FOV (Uses Cartopy) | | kwargs ** | Axis Polar settings. See [polar axis](axis.md) | -To plot based on hemisphere or selection of radars, here is an example plotting North hemisphere radars with selected SuperDARN Canada radars colored as green: +To plot based on hemisphere or selection of radars, here is an example plotting North hemisphere radars with selected SuperDARN Canada radars colored as green, note that the axes object (ax) needs to be updated inside to loop to plot multiple FOV: ```python +import pydarn +from datetime import datetime +import matplotlib.pyplot as plt + +ax = None for stid in pydarn.SuperDARNRadars.radars.keys(): if pydarn.SuperDARNRadars.radars[stid].hemisphere == pydarn.Hemisphere.North: if stid != 2: if stid in [66, 65, 6, 65, 5]: - pydarn.Fan.plot_fov(stid, datetime(2021, 2, 5, 12, 5), + _ , _, ax, _ = pydarn.Fan.plot_fov(stid, datetime(2021, 2, 5, 12, 5), radar_label=True, fov_color='green', - line_color='green', alpha=0.8) + line_color='green', alpha=0.8, ax=ax) - pydarn.Fan.plot_fov(stid, datetime(2021, 2, 5, 12, 5), + _ , _, ax, _ = pydarn.Fan.plot_fov(stid, datetime(2021, 2, 5, 12, 5), radar_label=True, fov_color='blue', - line_color='blue', alpha=0.2, lowlat=10) + line_color='blue', alpha=0.2, lowlat=10, ax=ax) plt.show() ``` @@ -81,34 +89,78 @@ plt.show() This example will plot the South Hemisphere radars FOV in red: ```python +import pydarn +from datetime import datetime +import matplotlib.pyplot as plt + +ax = None for stid in pydarn.SuperDARNRadars.radars.keys(): if pydarn.SuperDARNRadars.radars[stid].hemisphere == pydarn.Hemisphere.South: if stid != 2: - pydarn.Fan.plot_fov(stid, datetime(2021, 2, 5, 12, 5), + _, _, ax, _ = pydarn.Fan.plot_fov(stid, datetime(2021, 2, 5, 12, 5), radar_label=True, fov_color='red', - line_color='red', alpha=0.2) + line_color='red', alpha=0.2, ax=ax) plt.show() ``` ![](../imgs/fov_3.png) -This example shows the use of *cartopy* and plotting in geographic coordinates. +This example shows the use of *cartopy*, plotting in geographic coordinates with the coastline outlines. ```python +import pydarn +from datetime import datetime +import matplotlib.pyplot as plt + _ , _, ax, ccrs = pydarn.Fan.plot_fov(stid=65, - date=dt.datetime(2022, 1, 8, 14, 5), + date=datetime(2022, 1, 8, 14, 5), fov_color='red', coords=pydarn.Coords.GEOGRAPHIC, - projs=pydarn.Projs.GEO) -ax.coastlines() + projs=pydarn.Projs.GEO, + coastline=True) plt.show() ``` ![](../imgs/fov_7.png) +!!! Note + If plotting multiple FOV on one plot for geographic coordinates, it is required that the `ax` and `ccrs` of the initial plot is read into the subsequent plots. Each subsequent plot also needs to be told that it is supposed to be in geographic coordinates and/or geographic projection using the `coords` and `projs` keywords. + +This is an example of multiple FOV in geographic coordinates using the correct set of keywords. + +```python +import pydarn +from datetime import datetime +import matplotlib.pyplot as plt + +_, _, ax, ccrs=pydarn.Fan.plot_fov(66, datetime(2021, 6, 21, 6, 0), + lowlat= 50, boundary=True, radar_label=True, + line_color='red', grid = True, + coords=pydarn.Coords.GEOGRAPHIC, + projs=pydarn.Projs.GEO, coastline=True) +pydarn.Fan.plot_fov(5, datetime(2021, 2, 5, 12, 5), radar_label=True, + ax=ax, ccrs=ccrs, boundary=True, line_color='blue', + grid = True, coords=pydarn.Coords.GEOGRAPHIC, + projs=pydarn.Projs.GEO) +pydarn.Fan.plot_fov(64, datetime(2021, 2, 5, 12, 5), radar_label=True, + ax=ax, ccrs=ccrs, boundary=True, line_color='green', + grid = True, coords=pydarn.Coords.GEOGRAPHIC, + projs=pydarn.Projs.GEO) +pydarn.Fan.plot_fov(65, datetime(2021, 2, 5, 12, 5), radar_label=True, + ax=ax, ccrs=ccrs, boundary=True, line_color='orange', + grid = True, coords=pydarn.Coords.GEOGRAPHIC, + projs=pydarn.Projs.GEO) +pydarn.Fan.plot_fov(6, datetime(2021, 2, 5, 12, 5), radar_label=True, + ax=ax, ccrs=ccrs, boundary=True, grid = True, + coords=pydarn.Coords.GEOGRAPHIC, projs=pydarn.Projs.GEO) +plt.show() +``` + +![](../imgs/fov_8.png_) + !!! Warning - Currently you cannot plot AACGM coordinates on a geographic plot as its not correctly transformed. Currently in development. + You cannot plot AACGM coordinates on a geographic plot as its not correctly transformed. `plot_fov` use two other plotting methods `plot_radar_position` and `plot_radar_label`, these methods have the following parameters: @@ -120,13 +172,18 @@ plt.show() | line_color=(string) | Sets the text and radar location dot color (default: black) | !!! Note - These methods do not plot on a polar axis so it is strongly encouraged to use `plot_fov` to use them. + These methods will not plot on a polar axis if called without `plot_fov`, so it is strongly encouraged to use `plot_fov` to use them. To obtain only dots and labels: ```python +import pydarn +from datetime import datetime +import matplotlib.pyplot as plt + pydarn.Fan.plot_fov(66, datetime(2021, 2, 5, 12, 5), boundary=False, radar_label=True) +plt.show() ``` ![](../imgs/fov_4.png) diff --git a/docs/user/grid.md b/docs/user/grid.md index 7d1b08571..72ea82fcf 100644 --- a/docs/user/grid.md +++ b/docs/user/grid.md @@ -81,7 +81,7 @@ Here is a list of all the current options than can be used with `plot_grid` | start_time=(datetime.datetime) | The start time of the record to plot | | time_delta=(int) | How close to the start time to be to the start time of the record | | lowlat=(int) | Control the lower latitude boundary of the plot (default 30/-30 AACGM lat) | -| fov=(bool) | Boolean to show the Field-of-View of the radar(s) | +| boundary=(bool) | Boolean to show the Field-of-View of the radar(s) | | cmap=matplotlib.cm | A matplotlib color map object. Will override the pyDARN defaults for chosen parameter | | zmin=(int) | Minimum data value for colouring | | zmax=(int) | Maximum data value for colouring | @@ -91,7 +91,9 @@ Here is a list of all the current options than can be used with `plot_grid` | radar_label=(str) | To include a dot at radar location and label of 3 letter code | | fov_color=(str) | Fill color of fov | | line_color=(str) | Fill color of fov lines | - +| coastline=(bool) | Plots outlines of coastlines below grid data | +| coords=(Coords) | [Coordinates](coordinates.md) for the data to be plotted in | +| projs=(Projs) | Projections to plot the data on top of | As an example, the following code plots multiple radar Grid plot: ```python diff --git a/docs/user/install.md b/docs/user/install.md index 530c48f73..d177c4d60 100644 --- a/docs/user/install.md +++ b/docs/user/install.md @@ -57,7 +57,7 @@ pyDARN's setup will download the following dependencies: - [AACGMv2](https://pypi.org/project/aacgmv2/) !!! Note - If you wish to plot coastlines you will need to install cartopy>=0.19 separately + If you wish to plot coastlines or geographic projections you will need to install cartopy>=0.19 separately ### Cartopy [Cartopy](https://scitools.org.uk/cartopy/docs/latest/) is a Python package designed for geospatial data processing in order to produce maps and other geospatial data analyses. This library is used when invoking a projection system needing overlapped coastline maps in Fan, Grid and Map plots. @@ -69,7 +69,7 @@ For installing cartopy please follow the packages [installation](https://scitool !!! Note - cartopy is a challenging package to install so please provide any information on troubleshoot or solutions to common issue on [pyDARN github](https://github.com/SuperDARN/pydarn) page. + cartopy can be a challenging package to install so please provide any information on troubleshooting or solutions to common issues on the [pyDARN github](https://github.com/SuperDARN/pydarn) page. diff --git a/docs/user/map.md b/docs/user/map.md index c19146295..50e686315 100644 --- a/docs/user/map.md +++ b/docs/user/map.md @@ -53,7 +53,7 @@ map_data = SDarn_read.read_map() ``` With the map data loaded as a list of dictionaries (`map_data` variable in above example), you may now call the `plot_mapdata` method. Make sure you tell the method what time, in [`datetime` format], or record number (numbered from first recorded in file, counting from 0): ```python -mapplot = pydarn.Map.plot_mapdata(map_data, record=150) +mapplot = pydarn.Maps.plot_mapdata(map_data, record=150) plt.show() ``` @@ -62,7 +62,7 @@ In this example, the record at 150 was plotted with the defaulted parameter, `Ma You might have noticed that the variable `mapplot` in the examples above actually holds some information. This contains the AACGM latitude and longitude of the mapped vectors plotted. If you instead change `mapplot` to 3 separate variables, it will return the latitude, longitude, and data info into separate variables: ```python -lats,lons,data=pydarn.Map.plot_mapdata(map_data, start_time=stime) +lats,lons,data=pydarn.Maps.plot_mapdata(map_data, start_time=stime) ``` ### Additional options @@ -81,6 +81,7 @@ Here is a list of all the current options than can be used with `plot_mapdata` | cmap=matplotlib.cm | A matplotlib color map object. Will override the pyDARN defaults for chosen parameter | | zmin=(int) | Minimum data value for colouring | | zmax=(int) | Maximum data value for colouring | +| color_vectors=(bool) | Choose if the vectors are plotted with corresponding color map (True), or in black | | colorbar=(bool) | Set true to plot a colorbar (default: True) | | colorbar_label=(string) | Label for the colour bar (requires colorbar to be true) | | title=(str) | To add a title to the plot | @@ -95,6 +96,9 @@ Here is a list of all the current options than can be used with `plot_mapdata` | contour_fill_cmap=(str) | If contour_fill=True, color map can be selected (default 'RdBu') | | contour_colorbar_label=(str) | If contour_fill and contour_colorbar= True, set custom contour colorbar label | | pot_minmax_color=(str) | Choose color of minimum and maximum potential markers | +| radar_location=(bool) | Show locations of radars used in the map file | +| reference_vector=(int) | If value given, reference vector with given value is plotted, remove using False or 0 | +| coastline=(bool) | Show coastlines under convection data (uses Cartopy) | More `**kwargs` can be used to customise the display of the radars field-of-view if `boundary=True` diff --git a/docs/user/range_time.md b/docs/user/range_time.md index 9ed484b28..6dc4b8e74 100644 --- a/docs/user/range_time.md +++ b/docs/user/range_time.md @@ -97,7 +97,11 @@ To see all the customisation options, check out all the parameters listed in 'rt | date_fmt=(string) | How the x-tick labels look. Default is ('%y/%m/%d\n %H:%M') | | zmin=(int) | Minimum data value to be plotted | | zmax=(int) | Maximum data value to be plotted | -| range_estimation=(RangeEstimation) | Coordinates to use for the y-axis (See [Coordinates](coordinates.md)) | +| range_estimation=(RangeEstimation) | Estaimtion of the distance for the radar to use for the y-axis (See [Ranges, Coords and Projs](coordinates.md)) | +| coords=(Coords) | Used in conjunction with range_estimation, converts the y-axis to a coordinate | +| lat_or_lon=(str) | In conjunction with coords, choose if you would like the latitude ('lat') or longitude ('lon') | +| colorbar=(plt.colorbar) | If you would like a different colorbar than the default | +| colorbar_label=(str) | Set the label fo the colorbar | For instance, code for a velocity RTP showing the same beam of Clyde river radar as above, but with ground scatter plotted in grey, date format as `hh:mm`, custom min and max values and a colour bar label could look something like: ```python @@ -112,6 +116,16 @@ which outputs: and looks much more useful! +Plotting: +```python +pydarn.RTP.plot_range_time(fitacf_data, beam_num=fitacf_data[0]['bmnum'], groundscatter=True, + zmax=500, zmin=-500, date_fmt='%H:%M', + colorbar_label='Line-of-Sight Velocity (m s$^{-1}$)', + range_estimation=pydarn.RangeEstimation.SLANT_RANGE, + coords=pydarn.Coords.GEOGRAPHIC, lat_or_lon='lat') +``` +will produce the above plot with geographic latitude along the y-axis instead. + #### Plotting with a custom color map Because the default parameter plotted is line-of-sight velocity, there is also a special red-blue colour map set as default (as seen above) which is only meant for velocity RTP's. diff --git a/docs/user/summary.md b/docs/user/summary.md index d5230e55c..9fe8849ad 100644 --- a/docs/user/summary.md +++ b/docs/user/summary.md @@ -86,5 +86,6 @@ Other common options include: | channel=(int) | Specify channel number (default=all) | | watermark=(bool) | True adds a 'not for publication' watermark | | cmaps=(dict/str) | Specifies the colour maps used in plotting | -| range_estimation=(RangeEstimation) | Coordinates to use for the y-axis (See [Coordinates](coordinates.md)) | -For more options on how to modify plot_summary, take a look at the method in `rtp.py`. +| range_estimation=(RangeEstimation) | Range use for the y-axis (See [Ranges, Coords and Projs](coordinates.md)) | +For more options on how to modify plot_summary, take a look at the method in `rtp.py`. +All options available in time-series and range-time plots can be used in summary plots. diff --git a/docs/user/time_series.md b/docs/user/time_series.md index d9132a2d8..29873b16b 100644 --- a/docs/user/time_series.md +++ b/docs/user/time_series.md @@ -41,7 +41,10 @@ In a similar way to RTP, you also have access to numerous plotting options: | end_time=(datetime object) | Control the end time of the plot | | date_fmt=(string) | How the x-tick labels look. Default is ('%y/%m/%d\n %H:%M') | | channel=(int or string) | Choose which channel to plot. Default is 'all'. | -| cp_name=(bool) | Print the name of the cpid when plotting cpid timeseries' | +| cp_name=(bool) | Print the name of the cpid when plotting cpid timeseries' | +| color=(str) | Color of the line plot | +| linestyle=(str) | Style of line plotted | +| linewidth=(float) | Thickness of plotted line | For example, checking out the cpid's for a 24hour Clyde FITACF file: diff --git a/mkdocs.yml b/mkdocs.yml index dcdb49749..1897921ab 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,13 +12,13 @@ nav: - SuperDARN Data Access: user/superdarn_data.md - Citing: user/citing.md - Tutorials: - - Radar and Hardware Information: user/hardware.md - - Coordinates: user/coordinates.md - Reading Files: user/io.md + - Radar and Hardware Information: user/hardware.md + - Axes Setup: user/axis.md + - Ranges, Coords and Projs: user/coordinates.md - Range-Time plots: user/range_time.md - Time-Series plots: user/time_series.md - Summary plots: user/summary.md - - Axis: user/axis.md - FOV plots: user/fov.md - Fan plots: user/fan.md - Grid plots: user/grid.md diff --git a/pydarn/.DS_Store b/pydarn/.DS_Store deleted file mode 100644 index 1b3561887..000000000 Binary files a/pydarn/.DS_Store and /dev/null differ diff --git a/pydarn/__pycache__/__init__.cpython-34.pyc b/pydarn/__pycache__/__init__.cpython-34.pyc deleted file mode 100644 index d55a930fd..000000000 Binary files a/pydarn/__pycache__/__init__.cpython-34.pyc and /dev/null differ diff --git a/pydarn/exceptions/__pycache__/__init__.cpython-34.pyc b/pydarn/exceptions/__pycache__/__init__.cpython-34.pyc deleted file mode 100644 index 6f1321ee5..000000000 Binary files a/pydarn/exceptions/__pycache__/__init__.cpython-34.pyc and /dev/null differ diff --git a/pydarn/exceptions/__pycache__/pydmap_exceptions.cpython-34.pyc b/pydarn/exceptions/__pycache__/pydmap_exceptions.cpython-34.pyc deleted file mode 100644 index e52fc77a2..000000000 Binary files a/pydarn/exceptions/__pycache__/pydmap_exceptions.cpython-34.pyc and /dev/null differ diff --git a/pydarn/plotting/acf.py b/pydarn/plotting/acf.py index 6189fb73d..96c0cfce4 100644 --- a/pydarn/plotting/acf.py +++ b/pydarn/plotting/acf.py @@ -1,7 +1,20 @@ # Copyright (C) 2020 SuperDARN Canada, University of Saskatchewan # Author: Marina Schmidt -# This code is improvement based on acf.py in the DaVitpy library +# This code is based on acf.py in the DaVitpy library # https://github.com/vtsuperdarn/davitpy/blob/master/davitpy +# Modifications: +# 2022-05-03: CJM - Added options to plot power and phase of acf +# - change defaults to fit needs +# +# Disclaimer: +# pyDARN is under the LGPL v3 license found in the root directory LICENSE.md +# Everyone is permitted to copy and distribute verbatim copies of this license +# document, but changing it is not allowed. +# +# This version of the GNU Lesser General Public License incorporates the terms +# and conditions of version 3 of the GNU General Public License, +# supplemented by the additional permissions listed below. + import copy import matplotlib.pyplot as plt @@ -46,11 +59,11 @@ def __str__(self): @classmethod def plot_acfs(cls, dmap_data: List[dict], beam_num: int = 0, gate_num: int = 15, parameter: str = 'acfd', - scan_num: int = 0, start_time: datetime = None, ax=None, - normalized: bool = True, real_color: str = 'red', + scan_num: int = 0, start_time: datetime = None, + normalized: bool = False, real_color: str = 'red', plot_blank: bool = True, blank_marker: str = 'o', imaginary_color: str = 'blue', legend: bool = True, - **kwargs): + pwr_and_phs: bool = True, **kwargs): """ plots the parameter ACF/XCF field from SuperDARN file, typically RAWACF format for a given beam and gate number @@ -76,7 +89,7 @@ def plot_acfs(cls, dmap_data: List[dict], beam_num: int = 0, normalized: bool normalizes the parameter data with the associated power 0 value for the given gate number - default: True + default: False real_color: str line color of the real part of the paramter default: red @@ -92,6 +105,9 @@ def plot_acfs(cls, dmap_data: List[dict], beam_num: int = 0, legend: bool produces a standard legend default: True + pwr_and_phs: bool + plots the power and phase of the ACF + default: True kwargs: dict are applied to the real and imaginary plots @@ -110,11 +126,6 @@ def plot_acfs(cls, dmap_data: List[dict], beam_num: int = 0, ------- """ - # If an axes object is not passed in then store - # the equivalent object in matplotlib. This allows - # for variant matplotlib plotting styles. - if not ax: - ax = plt.gca() # Determine if a DmapRecord was passed in, instead of a list try: # because of partial records we need to find the first @@ -209,13 +220,6 @@ def plot_acfs(cls, dmap_data: List[dict], beam_num: int = 0, raise plot_exceptions.OutOfRangeGateError(parameter, gate_num, record['nrang']) - if normalized: - blank_re /= record['pwr0'][gate_num] - blank_im /= record['pwr0'][gate_num] - - re /= record['pwr0'][gate_num] - im /= record['pwr0'][gate_num] - # generates gaps where there are nan's masked_re = np.ma.array(re) masked_re = np.ma.masked_where(np.isnan(masked_re), masked_re) @@ -223,34 +227,119 @@ def plot_acfs(cls, dmap_data: List[dict], beam_num: int = 0, masked_im = np.ma.array(im) masked_im = np.ma.masked_where(np.isnan(masked_im), masked_im) - # plot real and imaginary + # Calculate pwr and phs regardless of choice as + # in return statement + pwr = np.sqrt(np.square(masked_re) + np.square(masked_im)) + phs = np.arctan2(masked_im, masked_re) + + masked_pwr = np.ma.array(pwr) + masked_pwr = np.ma.masked_where(np.isnan(masked_pwr), masked_pwr) + masked_phs = np.ma.array(phs) + masked_phs = np.ma.masked_where(np.isnan(masked_phs), masked_phs) + + if normalized is True: + masked_re /= record['pwr0'][gate_num] + masked_im /= record['pwr0'][gate_num] + + blank_re_n = blank_re / record['pwr0'][gate_num] + blank_im_n = blank_im / record['pwr0'][gate_num] + + # plot real and imaginary with power + if pwr_and_phs is True: + fig = plt.gcf() + gs = fig.add_gridspec(2, 2) + ax = fig.add_subplot(gs[0, :]) + ax_pwr = fig.add_subplot(gs[1, 0]) + ax_phs = fig.add_subplot(gs[1, 1]) + + ax_pwr.scatter(lags, masked_pwr, marker='o', color='tab:orange', + label='Power', **kwargs) + ax_pwr.set_ylabel('Power') + ax_pwr.set_xlabel('Lag Number') + ax_pwr.xaxis.set_major_locator(ticker.MaxNLocator(integer=True)) + ax_pwr.set_ylim([0, abs(max(masked_pwr)) + + 0.1*abs(max(masked_pwr))]) + ax_pwr.set_title('Power') + + ax_phs.scatter(lags, np.degrees(masked_phs), marker='o', + color='tab:purple', label='Phase', **kwargs) + ax_phs.set_ylabel('Phase (degrees)') + ax_phs.set_xlabel('Lag Number') + ax_phs.xaxis.set_major_locator(ticker.MaxNLocator(integer=True)) + ax_phs.set_ylim([-180, 180]) + ax_phs.set_title('Phase') + else: + ax = plt.gca() + ax.plot(lags, masked_im, marker='o', color=imaginary_color, label='Imaginary', **kwargs) ax.plot(lags, masked_re, marker='o', color=real_color, label='Real', **kwargs) - # plot blanked lags + # Plot blanked lags if plot_blank: - print("plotting") - print(blanked_lags) + print("Blanked lags:", blanked_lags) for blank in blanked_lags: # I use scatter here to make points not lines # also shows up in the legend nicer - line_re = ax.scatter(blank, blank_re[lags.index(blank)], - edgecolors=real_color, facecolors='white', - marker=blank_marker) - line_im = ax.scatter(blank, blank_im[lags.index(blank)], - edgecolors=imaginary_color, - facecolors='white', marker=blank_marker) + if pwr_and_phs is True: + blank_pwr = np.sqrt(np.square(blank_re) + + np.square(blank_im)) + blank_phs = np.arctan2(blank_im, blank_re) + line_pwr = ax_pwr.scatter(blank, + blank_pwr[lags.index(blank)], + edgecolors='tab:orange', + facecolors=(1, 1, 1, 0), + marker='o') + line_phs = ax_phs.scatter(blank, + np.degrees( + blank_phs[lags.index(blank)]), + edgecolor='tab:purple', + marker='o', + facecolor=(1, 1, 1, 0)) + + if normalized is True: + line_im = ax.scatter(blank, blank_im_n[lags.index(blank)], + edgecolors=imaginary_color, + facecolors=(1, 1, 1, 0), + marker=blank_marker) + line_re = ax.scatter(blank, blank_re_n[lags.index(blank)], + edgecolors=real_color, + facecolors=(1, 1, 1, 0), + marker=blank_marker) + else: + line_im = ax.scatter(blank, blank_im[lags.index(blank)], + edgecolors=imaginary_color, + facecolors=(1, 1, 1, 0), + marker=blank_marker) + line_re = ax.scatter(blank, blank_re[lags.index(blank)], + edgecolors=real_color, + facecolors=(1, 1, 1, 0), + marker=blank_marker) # generate generic legend if legend and blanked_lags != []: line_re.set_label('Real Blanked') line_im.set_label('Imaginary Blanked') - ax.legend() + if pwr_and_phs is True: + line_pwr.set_label('Power Blanked') + line_phs.set_label('Phase Blanked') + + # Make legend on right side of figure + if legend is True: + fig = plt.gcf() + fig.legend(loc=5) + plt.subplots_adjust(right=0.8) + # Set labels of main plot ax.set_ylabel(parameter) ax.set_xlabel('Lag Number') + # Calc and set limit of main plot + lim_val = max(abs(masked_re + masked_im))\ + + 0.1 * max(abs(masked_re + masked_im)) + ax.set_ylim([-lim_val, lim_val]) ax.xaxis.set_major_locator(ticker.MaxNLocator(integer=True)) + + # Set title of Plot radar_name = SuperDARNRadars.radars[cls.dmap_data[0]['stid']].name title = "{date} UT {radar} Beam {beam}, Gate {gate}, Control "\ "Program: {cpid}"\ @@ -259,6 +348,8 @@ def plot_acfs(cls, dmap_data: List[dict], beam_num: int = 0, cpid=record['cp']) ax.set_title(title) + return masked_re, masked_im, masked_pwr, masked_phs, blanked_lags + @classmethod def __found_scan(cls, scan_num: int, count_num: int, start_time: datetime, time: datetime): @@ -295,6 +386,7 @@ def __found_scan(cls, scan_num: int, count_num: int, def __blanked_lags(cls, record: dict, lags: list, gate: int): """ Determines the blanked lags in the data record + Lags contaminated by transmit pulse overlap Parameters ---------- diff --git a/pydarn/plotting/fan.py b/pydarn/plotting/fan.py index e387e4f70..3db6c7b0a 100644 --- a/pydarn/plotting/fan.py +++ b/pydarn/plotting/fan.py @@ -310,7 +310,7 @@ def plot_fan(cls, dmap_data: List[dict], ax=None, ranges: List = [], stid = dmap_data[0]['stid'] kwargs['hemisphere'] = SuperDARNRadars.radars[stid].hemisphere - ax, ccrs = projs(date=date, **kwargs) + ax, ccrs = projs(date=date, ax=ax, **kwargs) if ccrs is None: transform = ax.transData @@ -327,7 +327,8 @@ def plot_fan(cls, dmap_data: List[dict], ax=None, ranges: List = [], ax.pcolormesh(thetas, rs, np.ma.masked_array(grndsct, ~grndsct.astype(bool)), - norm=norm, cmap='Greys', transform=transform) + norm=norm, cmap='Greys', + transform=transform, zorder=3) if ccrs is None: azm = np.linspace(0, 2 * np.pi, 100) r, th = np.meshgrid(rs, azm) @@ -346,8 +347,12 @@ def plot_fan(cls, dmap_data: List[dict], ax=None, ranges: List = [], integer=True, nbins='auto') ticks = locator.tick_values(vmin=zmin, vmax=zmax) - cb = ax.figure.colorbar(mappable, ax=ax, - extend='both', ticks=ticks) + if zmin == 0: + cb = ax.figure.colorbar(mappable, ax=ax, extend='max', + ticks=ticks) + else: + cb = ax.figure.colorbar(mappable, ax=ax, extend='both', + ticks=ticks) if colorbar_label != '': cb.set_label(colorbar_label) diff --git a/pydarn/plotting/grid.py b/pydarn/plotting/grid.py index 52895c2a1..634b5fb27 100644 --- a/pydarn/plotting/grid.py +++ b/pydarn/plotting/grid.py @@ -15,7 +15,7 @@ """ -Grid plots, mapped to AACGM coordinates in a polar format +Grid plots """ import datetime as dt @@ -29,15 +29,8 @@ # Third party libraries import aacgmv2 -from pydarn import (PyDARNColormaps, Fan, plot_exceptions, - standard_warning_format) - -try: - from cartopy.mpl import geoaxes - import cartopy.crs as ccrs - cartopyInstalled = True -except Exception: - cartopyInstalled = False +from pydarn import (PyDARNColormaps, Fan, plot_exceptions, Hemisphere, + standard_warning_format, Projs, Coords) warnings.formatwarning = standard_warning_format @@ -63,6 +56,8 @@ def plot_grid(cls, dmap_data: List[dict], record: int = 0, zmax: int = None, colorbar: bool = True, colorbar_label: str = '', title: str = '', len_factor: float = 150.0, ref_vector: int = 300, + projs: Projs = Projs.POLAR, + coords: Coords = Coords.AACGM_MLT, **kwargs): """ Plots a radar's gridded vectors from a GRID file @@ -118,6 +113,12 @@ def plot_grid(cls, dmap_data: List[dict], record: int = 0, ref_vector: int Velocity value to be used for the reference vector, in m/s Default: 300 + projs: Enum + choice of projection for plot + default: Projs.POLAR (polar projection) + coords: Enum + choice of plotting coordinates + default: Coords.AACGM_MLT (Magnetic Lat and MLT) kwargs: key=value uses the parameters for plot_fov and projections.axis See Also @@ -130,13 +131,13 @@ def plot_grid(cls, dmap_data: List[dict], record: int = 0, thetas - List of gridded data point magnetic local times (degrees) end_thetas - List of magnetic local time end points used for vector plotting (degrees) - rs - List of gridded data point radius' (AACGM latitude) - end_rs - List of radius end points for vector plotting (AACGM latitude) + rs - List of gridded data point radius' (latitude) + end_rs - List of radius end points for vector plotting (latitude) data - List of magnitudes of line-of-sight velocity azm_v - List of azimuths of line-of-sight velocity else: thetas - List of gridded data point magnetic local times (degrees) - rs - List of gridded data point radius' (AACGM latitude) + rs - List of gridded data point radius' (latitude) data - List of data magnitudes plotted, for parameter chosen """ # Short hand for the parameters in GRID files @@ -147,10 +148,10 @@ def plot_grid(cls, dmap_data: List[dict], record: int = 0, if start_time is not None: for record in range(len(dmap_data)): date = dt.datetime(dmap_data[record]['start.year'], - dmap_data[record]['start.month'], - dmap_data[record]['start.day'], - dmap_data[record]['start.hour'], - dmap_data[record]['start.minute']) + dmap_data[record]['start.month'], + dmap_data[record]['start.day'], + dmap_data[record]['start.hour'], + dmap_data[record]['start.minute']) time_diff = date - start_time if time_diff.seconds/60 <= time_delta: break @@ -158,106 +159,187 @@ def plot_grid(cls, dmap_data: List[dict], record: int = 0, raise plot_exceptions.NoDataFoundError(parameter, start_time=start_time) else: - record = 0 + # Record is read in or default to 0 date = dt.datetime(dmap_data[record]['start.year'], - dmap_data[record]['start.month'], - dmap_data[record]['start.day'], - dmap_data[record]['start.hour'], - dmap_data[record]['start.minute']) + dmap_data[record]['start.month'], + dmap_data[record]['start.day'], + dmap_data[record]['start.hour'], + dmap_data[record]['start.minute']) with warnings.catch_warnings(): warnings.simplefilter("ignore") + # Hemisphere is not found in grid files so take from latitudes + hemisphere = Hemisphere(np.sign( + dmap_data[record]['vector.mlat'][0])) + ax, ccrs = projs(date=date, ax=ax, hemisphere=hemisphere, **kwargs) + if ccrs is None: + transform = ax.transData + else: + transform = ccrs.PlateCarree() + for stid in dmap_data[record]['stid']: - _, aacgm_lons, ax, _ =\ - Fan.plot_fov(stid, date, - ax=ax, **kwargs) - try: - data_lons = dmap_data[record]['vector.mlon'] - data_lats = dmap_data[record]['vector.mlat'] - except KeyError: - raise plot_exceptions.PartialRecordsError('vector.mlon') - - # Hold the beam positions - shifted_mlts = aacgm_lons[0, 0] - \ - (aacgmv2.convert_mlt(aacgm_lons[0, 0], date) * 15) - shifted_lons = data_lons - shifted_mlts + _, coord_lons, ax, ccrs =\ + Fan.plot_fov(stid, date, ax=ax, ccrs=ccrs, + coords=coords, projs=projs, **kwargs) + try: + data_lons = dmap_data[record]['vector.mlon'] + data_lats = dmap_data[record]['vector.mlat'] + except KeyError: + raise plot_exceptions.PartialRecordsError('vector.mlon') + + if coords != Coords.GEOGRAPHIC and projs == Projs.GEO: + raise plot_exceptions.NotImplemented( + "AACGM coordinates are" + " not implemented for " + " geographic projections right" + " now, if you would like to" + " see it sooner please help" + " out at " + "https://github.com" + "/SuperDARN/pyDARN") + if coords == Coords.GEOGRAPHIC and projs == Projs.POLAR: + raise plot_exceptions.NotImplemented( + "Geographic coordinates are" + " not implemented for " + " polar projections in grid plots" + " right now, if you would like to" + " see it sooner please help" + " out at " + "https://github.com" + "/SuperDARN/pyDARN") + # Thetas in aacgm required for calculating the + # vectors later, called them theta_calc and rs_calc + # for later use + shifted_mlts = coord_lons[0, 0] - \ + (aacgmv2.convert_mlt(coord_lons[0, 0], date) * 15) + shifted_lons = data_lons - shifted_mlts + rs_calc = data_lats + + if projs != Projs.GEO: + # Convert to radians for polar plots + thetas_calc = np.radians(shifted_lons) thetas = np.radians(shifted_lons) rs = data_lats - - # Colour table and max value selection depending on - # parameter plotted Load defaults if none given - if cmap is None: - cmap = {'vector.pwr.median': 'plasma', - 'vector.vel.median': 'plasma_r', - 'vector.wdt.median': PyDARNColormaps.PYDARN_VIRIDIS} - cmap = plt.cm.get_cmap(cmap[parameter]) - - # Setting zmin and zmax - defaultzminmax = {'vector.pwr.median': [0, 50], - 'vector.vel.median': [0, 1000], - 'vector.wdt.median': [0, 250]} - if zmin is None: - zmin = defaultzminmax[parameter][0] - if zmax is None: - zmax = defaultzminmax[parameter][1] - - norm = colors.Normalize - norm = norm(zmin, zmax) - - # check to make sure the parameter is present in the file - # this may not be the case for wdt and pwr as you need -xtd - # option in make_grid - try: - data = dmap_data[record][parameter] - except KeyError: - raise plot_exceptions.UnknownParameterError(parameter, - grid=True) - # Plot the magnitude of the parameter - ax.scatter(thetas, rs, c=data, - s=2.0, vmin=zmin, vmax=zmax, zorder=5, cmap=cmap) - - # If the parameter is velocity then plot the LOS vectors - if parameter == "vector.vel.median": - - # Get the azimuths from the data - azm_v = dmap_data[record]['vector.kvect'] - - # Number of data points - num_pts = range(len(data)) - - # Angle to "rotate" each vector by to get into same - # reference frame Controlled by longitude, or "mltitude" - alpha = thetas - - # Convert initial positions to Cartesian - start_pos_x = (90 - rs) * np.cos(thetas) - start_pos_y = (90 - rs) * np.sin(thetas) - - # Resolve LOS vector in x and y directions, - # with respect to mag pole - # Gives zonal and meridional components of LOS vector - los_x = -data * np.cos(np.radians(-azm_v)) - los_y = -data * np.sin(np.radians(-azm_v)) - - # Rotate each vector into same reference frame - # following vector rotation matrix - # https://en.wikipedia.org/wiki/Rotation_matrix - vec_x = (los_x * np.cos(alpha)) - (los_y * np.sin(alpha)) - vec_y = (los_x * np.sin(alpha)) + (los_y * np.cos(alpha)) - - # New vector end points, in Cartesian - end_pos_x = start_pos_x + (vec_x / len_factor) - end_pos_y = start_pos_y + (vec_y / len_factor) - - # Convert back to polar for plotting - end_rs = 90 - (np.sqrt(end_pos_x**2 + end_pos_y**2)) - end_thetas = np.arctan2(end_pos_y, end_pos_x) - - # Plot the vectors + else: + # Convert to geographic coordinates + glats, glons, _ = aacgmv2.convert_latlon_arr( + data_lats, data_lons, 300, + date, method_code="A2G") + thetas_calc = np.radians(data_lons) + thetas = glons + rs = glats + + # Colour table and max value selection depending on + # parameter plotted Load defaults if none given + if cmap is None: + cmap = {'vector.pwr.median': 'plasma', + 'vector.vel.median': 'plasma_r', + 'vector.wdt.median': + PyDARNColormaps.PYDARN_VIRIDIS} + cmap = plt.cm.get_cmap(cmap[parameter]) + + # Setting zmin and zmax + defaultzminmax = {'vector.pwr.median': [0, 50], + 'vector.vel.median': [0, 1000], + 'vector.wdt.median': [0, 250]} + if zmin is None: + zmin = defaultzminmax[parameter][0] + if zmax is None: + zmax = defaultzminmax[parameter][1] + + norm = colors.Normalize + norm = norm(zmin, zmax) + + # check to make sure the parameter is present in the file + # this may not be the case for wdt and pwr as you need -xtd + # option in make_grid + try: + data = dmap_data[record][parameter] + except KeyError: + raise plot_exceptions.UnknownParameterError(parameter, + grid=True) + # Plot the magnitude of the parameter + ax.scatter(thetas, rs, c=data, s=2.0, vmin=zmin, vmax=zmax, + zorder=5, cmap=cmap, transform=transform) + + # If the parameter is velocity then plot the LOS vectors + if parameter == "vector.vel.median": + + # Get the azimuths from the data + azm_v = dmap_data[record]['vector.kvect'] + + # Number of data points + num_pts = range(len(data)) + + # Angle to "rotate" each vector by to get into same + # reference frame Controlled by longitude, or "mltitude" + alpha = thetas_calc + + # Convert initial positions to Cartesian + start_pos_x = (90 - abs(rs_calc)) * np.cos(thetas_calc) + start_pos_y = (90 - abs(rs_calc)) * np.sin(thetas_calc) + + # Resolve LOS vector in x and y directions, + # with respect to mag pole + # Gives zonal and meridional components of LOS vector + los_x = -data * np.cos(np.radians( + -azm_v * hemisphere.value)) + los_y = -data * np.sin(np.radians( + -azm_v * hemisphere.value)) + + # Rotate each vector into same reference frame + # following vector rotation matrix + # https://en.wikipedia.org/wiki/Rotation_matrix + vec_x = (los_x * np.cos(alpha)) - (los_y * np.sin(alpha)) + vec_y = (los_x * np.sin(alpha)) + (los_y * np.cos(alpha)) + + # New vector end points, in Cartesian + end_pos_x = start_pos_x + (vec_x * hemisphere.value / + len_factor) + end_pos_y = start_pos_y + (vec_y * hemisphere.value / + len_factor) + + # Convert back to polar for plotting + end_rs = 90 - (np.sqrt(end_pos_x**2 + end_pos_y**2)) + end_thetas = np.arctan2(end_pos_y, end_pos_x) + + end_rs = end_rs * hemisphere.value + + # Plot the vectors + if projs != Projs.GEO: for i in num_pts: plt.plot([thetas[i], end_thetas[i]], [rs[i], end_rs[i]], c=cmap(norm(data[i])), - linewidth=0.5) + linewidth=0.5, transform=transform) + else: + # If proj is geographic, convert the end points into + # geographic positions to plot + end_g_rs, end_g_thetas, _ = \ + aacgmv2.convert_latlon_arr(end_rs, + np.degrees(end_thetas), + 300, date, + method_code="A2G") + for i in num_pts: + # If the vector does not cross the meridian + if np.sign(thetas[i]) == np.sign(end_g_thetas[i]): + plt.plot([thetas[i], end_g_thetas[i]], + [rs[i], end_g_rs[i]], + c=cmap(norm(data[i])), + linewidth=0.5, transform=transform) + # If the vector crosses the meridian then amend so that + # the start and end are in the same sign + else: + if abs(end_g_thetas[i]) > 90: + if end_g_thetas[i] < 0: + end_g_thetas[i] = end_g_thetas[i] + 360 + else: + thetas[i] = thetas[i] + 360 + # Vector plots correctly over the 0 meridian so + # Nothing is done to correct that section + plt.plot([thetas[i], end_g_thetas[i]], + [rs[i], end_g_rs[i]], + c=cmap(norm(data[i])), + linewidth=0.5, transform=transform) # TODO: Add a velocity reference vector @@ -267,8 +349,12 @@ def plot_grid(cls, dmap_data: List[dict], record: int = 0, integer=True, nbins='auto') ticks = locator.tick_values(vmin=zmin, vmax=zmax) - cb = ax.figure.colorbar(mappable, ax=ax, - extend='both', ticks=ticks) + if zmin == 0: + cb = ax.figure.colorbar(mappable, ax=ax, extend='max', + ticks=ticks) + else: + cb = ax.figure.colorbar(mappable, ax=ax, extend='both', + ticks=ticks) if colorbar_label != '': cb.set_label(colorbar_label) diff --git a/pydarn/plotting/maps.py b/pydarn/plotting/maps.py index 683c3c287..265b5366d 100644 --- a/pydarn/plotting/maps.py +++ b/pydarn/plotting/maps.py @@ -3,10 +3,14 @@ # Copyright (C) 2012 VT SuperDARN Lab # Modifications: # 2022-03-08: MTS - added partial records exception -# 2021-03-18: CJM - Included contour plotting and HMB -# 2021-03-31: CJM - Map info included -# 2021-03-31: CJM - IMF clock angle dial added -# 2021-04-01: CJM - Bug fix for lon shifting to MLT +# 2022-03-18: CJM - Included contour plotting and HMB +# 2022-03-31: CJM - Map info included +# 2022-03-31: CJM - IMF clock angle dial added +# 2022-04-01: CJM - Bug fix for lon shifting to MLT +# 2022-04-28: CJM - Added option to have single color vectors with reference +# vector +# 2022-08-15: CJM - Removed plot_FOV call for default uses +# 2022-12-13: CJM - Limited reference vectors to only velocity use # # Disclaimer: # pyDARN is under the LGPL v3 license found in the root directory LICENSE.md @@ -29,7 +33,7 @@ from enum import Enum from matplotlib import ticker, cm, colors -from mpl_toolkits.axes_grid.inset_locator import InsetPosition +from mpl_toolkits.axes_grid1.inset_locator import InsetPosition from scipy import special from typing import List @@ -38,7 +42,7 @@ from pydarn import (PyDARNColormaps, plot_exceptions, standard_warning_format, Re, Hemisphere, - time2datetime, find_record, Fan, MapParams) + time2datetime, find_record, Fan, Projs, MapParams) warnings.formatwarning = standard_warning_format @@ -64,12 +68,14 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, parameter: Enum = MapParams.FITTED_VELOCITY, record: int = 0, start_time: dt.datetime = None, time_delta: float = 1, alpha: float = 1.0, - len_factor: float = 150, cmap: str = None, - colorbar: bool = True, colorbar_label: str = '', - title: str = '', zmin: float = None, zmax: float = None, + len_factor: float = 150, color_vectors: bool = True, + cmap: str = None, colorbar: bool = True, + colorbar_label: str = '', title: str = '', + zmin: float = None, zmax: float = None, hmb: bool = True, boundary: bool = False, radar_location: bool = False, map_info: bool = True, - imf_dial: bool = True, **kwargs): + imf_dial: bool = True, reference_vector: int = 500, + projs: Projs = Projs.POLAR, **kwargs): """ Plots convection maps data points and vectors @@ -93,6 +99,10 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, How close the start_time has to be start_time of the record in minutes default: 1 + color_vectors: bool + If True, color the vectors by color map chosen + If False, color dark grey and include vector reference + Default: True cmap: matplotlib.cm matplotlib colour map https://matplotlib.org/tutorials/colors/colormaps.html @@ -134,6 +144,16 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, imf_dial: bool If True, draw an IMF dial of the magnetic field clock angle. Default: True + reference_vector: int + If a value is given, a reference velocity vector with + magnitude of given value, drawn in lower right corner. + Vector can be turned off by setting this value to False or 0. + Will not plot for power or spectral width options. + Default: 500 (vector plotted) + projs: Enum + choice of projection for plot + default: Projs.POLAR (polar projection) + There is no support for other projections currently kwargs: key=value uses the parameters for plot_fov and projections.axis @@ -142,6 +162,7 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, if start_time is not None: record = find_record(dmap_data, start_time, time_delta) date = time2datetime(dmap_data[record]) + if cmap is None: cmap = {MapParams.FITTED_VELOCITY: 'plasma_r', MapParams.MODEL_VELOCITY: 'plasma_r', @@ -164,13 +185,27 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, norm = colors.Normalize norm = norm(zmin, zmax) + if projs != Projs.POLAR: + raise plot_exceptions.NotImplemented(" Only polar projections" + " are implemented for" + " convection maps." + " Please set" + " projs=Projs.POLAR" + " to plot a convection map.") + with warnings.catch_warnings(): warnings.simplefilter("ignore") - # TODO: Make FOV outlines optional or invisible - for stid in dmap_data[record]['stid']: - _, aacgm_lons, ax, _ =\ - Fan.plot_fov(stid, date, ax=ax, boundary=boundary, - radar_location=radar_location, **kwargs) + # If the user wants to plot a FOV boundary or radar location + # Needs to find the positions for each + # Else just call the axis maker: proj + if boundary or radar_location: + for stid in dmap_data[record]['stid']: + _, _, ax, _ =\ + Fan.plot_fov(stid, date, ax=ax, boundary=boundary, + radar_location=radar_location, + **kwargs) + else: + ax, _ = projs(date, ax=ax, hemisphere=hemisphere, **kwargs) if parameter == MapParams.MODEL_VELOCITY: try: @@ -185,12 +220,11 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, except KeyError: raise plot_exceptions.PartialRecordsError('model.mlat') - # Hold the beam positions - shifted_mlts = aacgm_lons[0, 0] - \ - (aacgmv2.convert_mlt(aacgm_lons[0, 0], date) * 15) + # Arbitrary lon used to calculate the shift required + shifted_mlts = 0 - (aacgmv2.convert_mlt(0, date) * 15) shifted_lons = data_lons - shifted_mlts # Note that this "mlons" is adjusted for MLT - mlons = np.radians(shifted_lons) + mlons = np.radians(shifted_lons) mlats = data_lats # If the parameter is velocity then plot the LOS vectors @@ -208,6 +242,7 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, lat_min=dmap_data[ record]['latmin'], len_factor=len_factor) + elif parameter == MapParams.MODEL_VELOCITY: v_mag = dmap_data[record]['model.vel.median'] azm_v = np.radians(dmap_data[record]['model.kvect']) @@ -224,9 +259,21 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, if parameter in [MapParams.FITTED_VELOCITY, MapParams.MODEL_VELOCITY, MapParams.RAW_VELOCITY]: + # Make reference vector and add it to the array to + # be calculated too + reflat = (np.abs(plt.gca().get_ylim()[1]) - 5) * hemisphere.value + reflon = np.radians(45) + v_mag = np.append(v_mag, reference_vector) + if hemisphere == Hemisphere.North: + azm_v = np.append(azm_v, np.radians(135)) + else: + azm_v = np.append(azm_v, np.radians(45)) + # Angle to "rotate" each vector by to get into same # reference frame Controlled by longitude, or "mltitude" - alpha = mlons + alpha = np.append(mlons, reflon) + mlons = np.append(mlons, reflon) + mlats = np.append(mlats, reflat) # Convert initial positions to Cartesian start_pos_x = (90 - abs(mlats)) * np.cos(mlons) @@ -245,48 +292,82 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, vec_y = (x * np.sin(alpha)) + (y * np.cos(alpha)) # New vector end points, in Cartesian - end_pos_x = start_pos_x + (vec_x * hemisphere.value / len_factor) - end_pos_y = start_pos_y + (vec_y * hemisphere.value / len_factor) + end_pos_x = start_pos_x + (vec_x * hemisphere.value / len_factor) + end_pos_y = start_pos_y + (vec_y * hemisphere.value / len_factor) # Convert back to polar for plotting end_mlats = 90.0 - (np.sqrt(end_pos_x**2 + end_pos_y**2)) end_mlons = np.arctan2(end_pos_y, end_pos_x) - - end_mlats=end_mlats * hemisphere.value - # Plot the vectomlats - for i in range(len(v_mag)): - plt.plot([mlons[i], end_mlons[i]], - [mlats[i], end_mlats[i]], c=cmap(norm(v_mag[i])), - linewidth=0.5, zorder=5.0) - plt.scatter(mlons, mlats, c=v_mag, s=2.0, - vmin=zmin, vmax=zmax, cmap=cmap, zorder=5.0) + end_mlats = end_mlats * hemisphere.value - # Plot potential contours - fit_coefficient = dmap_data[record]['N+2'] - fit_order = dmap_data[record]['fit.order'] - lat_shift = dmap_data[record]['lat.shft'] - lon_shift = dmap_data[record]['lon.shft'] - lat_min = dmap_data[record]['latmin'] - - cls.plot_potential_contours(fit_coefficient, lat_min, date, - lat_shift=lat_shift, lon_shift=lon_shift, - fit_order=fit_order, hemisphere=hemisphere, - **kwargs) + # Plot the vector socks (final vector is the reference + # vector to be plotted later if required) + if color_vectors is True: + for i in range(len(v_mag) - 1): + plt.plot([mlons[i], end_mlons[i]], + [mlats[i], end_mlats[i]], c=cmap(norm(v_mag[i])), + linewidth=0.5, zorder=5.0) + else: + for i in range(len(v_mag) - 1): + plt.plot([mlons[i], end_mlons[i]], + [mlats[i], end_mlats[i]], c='#292929', + linewidth=0.5, zorder=5.0) + + # Plot the sock start dots and reference vector if known + if color_vectors is True: + if parameter in [MapParams.FITTED_VELOCITY, + MapParams.MODEL_VELOCITY, + MapParams.RAW_VELOCITY]: + if reference_vector > 0: + plt.scatter(mlons[:], mlats[:], c=v_mag[:], s=2.0, + vmin=zmin, vmax=zmax, cmap=cmap, zorder=5.0, + clip_on=False) + plt.plot([mlons[-1], end_mlons[-1]], + [mlats[-1], end_mlats[-1]], c=cmap(norm(v_mag[-1])), + linewidth=0.5, zorder=5.0, clip_on=False) + plt.figtext(0.675, 0.15, str(reference_vector) + ' m/s', + fontsize=8) + else: + plt.scatter(mlons[:-1], mlats[:-1], c=v_mag[:-1], s=2.0, + vmin=zmin, vmax=zmax, cmap=cmap, zorder=5.0) + else: + plt.scatter(mlons[:], mlats[:], c=v_mag[:], s=2.0, + vmin=zmin, vmax=zmax, cmap=cmap, zorder=5.0) - if hmb is True: - # Plot the HMB - mlats_hmb = dmap_data[record]['boundary.mlat'] - mlons_hmb = dmap_data[record]['boundary.mlon'] - cls.plot_heppner_maynard_boundary(mlats_hmb, mlons_hmb, date) + else: + # no color so make sure colorbar is turned off + colorbar = False + if parameter in [MapParams.FITTED_VELOCITY, + MapParams.MODEL_VELOCITY, + MapParams.RAW_VELOCITY]: + if reference_vector > 0: + plt.scatter(mlons[:], mlats[:], c='#292929', s=2.0, + zorder=5.0, clip_on=False) + plt.plot([mlons[-1], end_mlons[-1]], + [mlats[-1], end_mlats[-1]], c='#292929', + linewidth=0.5, zorder=5.0, clip_on=False) + plt.figtext(0.675, 0.15, str(reference_vector) + ' m/s', + fontsize=8) + else: + plt.scatter(mlons[:-1], mlats[:-1], c='#292929', s=2.0, + zorder=5.0) + else: + plt.scatter(mlons[:], mlats[:], c='#292929', s=2.0, + zorder=5.0) if colorbar is True: mappable = cm.ScalarMappable(norm=norm, cmap=cmap) locator = ticker.MaxNLocator(symmetric=True, min_n_ticks=3, integer=True, nbins='auto') ticks = locator.tick_values(vmin=zmin, vmax=zmax) - cb = ax.figure.colorbar(mappable, ax=ax, - extend='both', ticks=ticks) + + if zmin == 0: + cb = ax.figure.colorbar(mappable, ax=ax, extend='max', + ticks=ticks) + else: + cb = ax.figure.colorbar(mappable, ax=ax, extend='both', + ticks=ticks) if colorbar_label != '': cb.set_label(colorbar_label) @@ -300,6 +381,24 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, elif parameter is MapParams.POWER: cb.set_label('Power') + # Plot potential contours + fit_coefficient = dmap_data[record]['N+2'] + fit_order = dmap_data[record]['fit.order'] + lat_shift = dmap_data[record]['lat.shft'] + lon_shift = dmap_data[record]['lon.shft'] + lat_min = dmap_data[record]['latmin'] + + cls.plot_potential_contours(fit_coefficient, lat_min, date, ax, + lat_shift=lat_shift, lon_shift=lon_shift, + fit_order=fit_order, hemisphere=hemisphere, + **kwargs) + + if hmb is True: + # Plot the HMB + mlats_hmb = dmap_data[record]['boundary.mlat'] + mlons_hmb = dmap_data[record]['boundary.mlon'] + cls.plot_heppner_maynard_boundary(mlats_hmb, mlons_hmb, date) + if title == '': title = "{year}-{month}-{day} {start_hour}:{start_minute} -"\ " {end_hour}:{end_minute}"\ @@ -583,7 +682,7 @@ def add_map_info(cls, fit_order: float, pol_cap_pot: float, @classmethod - def plot_heppner_maynard_boundary(cls, mlats: list, mlons: list, + def plot_heppner_maynard_boundary(cls, mlats: list, mlons: list, date: object, line_color: str = 'black', **kwargs): # TODO: No evaluation of coordinate system made! May need if in @@ -735,7 +834,6 @@ def calculate_potentials(cls, fit_coefficient: list, lat_min: list, # Adjusted/Normalised values (runs 0 - pi) alpha = np.pi / theta_max x = np.cos(alpha*theta) - # Legendre Polys for j, xj in enumerate(x): plm_tmp = special.lpmn(fit_order, fit_order, xj) @@ -783,7 +881,7 @@ def calculate_potentials(cls, fit_coefficient: list, lat_min: list, @classmethod def plot_potential_contours(cls, fit_coefficient: list, lat_min: list, - date: object, lat_shift: int = 0, + date: object, ax: object, lat_shift: int = 0, lon_shift: int = 0, fit_order: int = 6, hemisphere: Enum = Hemisphere.North, contour_levels: list = [], @@ -809,6 +907,8 @@ def plot_potential_contours(cls, fit_coefficient: list, lat_min: list, Not to be confused with 'lowlat' date: datetime object Date from record + ax: object + matplotlib axis object lat_shift: int Generic shift in latitude from map file default: 0 @@ -888,7 +988,7 @@ def plot_potential_contours(cls, fit_coefficient: list, lat_min: list, vmax=abs(pot_arr).max(), vmin=-abs(pot_arr).max(), locator=ticker.FixedLocator(contour_levels), - cmap=contour_fill_cmap, alpha=0.6, + cmap=contour_fill_cmap, alpha=0.5, extend='both', zorder=3.0) if contour_colorbar is True: norm = colors.Normalize @@ -898,7 +998,7 @@ def plot_potential_contours(cls, fit_coefficient: list, lat_min: list, integer=True, nbins='auto') ticks = locator.tick_values(vmin=-abs(pot_arr).max(), vmax=abs(pot_arr).max()) - cb = plt.colorbar(mappable, extend='both', ticks=ticks) + cb = plt.colorbar(mappable, ax=ax, extend='both', ticks=ticks) if contour_colorbar_label != '': cb.set_label(contour_colorbar_label) else: diff --git a/pydarn/plotting/projections.py b/pydarn/plotting/projections.py index ac65c0dff..d81abf65f 100644 --- a/pydarn/plotting/projections.py +++ b/pydarn/plotting/projections.py @@ -4,6 +4,9 @@ # Modifications: # 2022-03-11 MTS added enums for projection function calls # 2022-03-22 MTS removed coastline call and added grid lines to cartopy plotting +# 2022-05-20 CJM added options to plot coastlines +# 2022-06-13 Elliott Day don't create new ax if ax passed in to Projs +# # Disclaimer: # pyDARN is under the LGPL v3 license found in the root directory LICENSE.md # Everyone is permitted to copy and distribute verbatim copies of this license @@ -15,6 +18,7 @@ """ Code which generates axis objects for use in plotting functions """ +import aacgmv2 import enum import matplotlib.pyplot as plt import numpy as np @@ -25,6 +29,7 @@ import cartopy # from cartopy.mpl import geoaxes import cartopy.crs as ccrs + import cartopy.feature as cfeature cartopyInstalled = True if version.parse(cartopy.__version__) < version.parse("0.19"): cartopyVersion = False @@ -34,14 +39,54 @@ cartopyInstalled = False -def axis_polar(lowlat: int = 30, hemisphere: Hemisphere = Hemisphere.North, - grid_lines: bool = True, **kwargs): +def convert_geo_coastline_to_mag(geom, date, alt: float = 0.0): + ''' + Takes the geometry object of coastlines and converts + the coordinates into AACGM_MLT for convection maps only + Only required usage is for cartopys NaturalEarthFeature + at 110m resolution only + Parameters + ---------- + geom: Shapely Geometry object + A list/collection of geometry objects + date: datetime object + Date of required plot + alt: float + Altitude in km + Default 0 (sea level) for coastlines + ''' + # Iterate over the coordinates and convert to MLT + def convert_to_mag(coords, date, alt): + for glon, glat in geom.coords: + [mlat, lon_mag, _] = \ + aacgmv2.convert_latlon_arr(glat, glon, alt, + date, method_code='G2A') + # Shift to MLT + shifted_mlts = lon_mag[0] - \ + (aacgmv2.convert_mlt(lon_mag[0], date) * 15) + shifted_lons = lon_mag - shifted_mlts + mlon = np.radians(shifted_lons) + yield (mlon.item(), mlat.item()) + # Return geometry object + return type(geom)(list(convert_to_mag(geom.coords, date, alt))) + + +def axis_polar(date, ax: object = None, lowlat: int = 30, + hemisphere: Hemisphere = Hemisphere.North, + grid_lines: bool = True, coastline: bool = False, + **kwargs): + """ Plots a radar's Field Of View (FOV) fan plot for the given data and scan number Parameters ----------- + ax: matplotlib.pyplot axis + Pre-defined axis object to pass in, must be + polar projection + Default: Generates a polar projection for the user + with MLT/latitude labels lowlat: int Lower AACGM latitude boundary for the polar plot Default: 30 @@ -50,35 +95,77 @@ def axis_polar(lowlat: int = 30, hemisphere: Hemisphere = Hemisphere.North, Hemisphere.South for northern and southern hemispheres, respectively Default: Hemisphere.North + grid_lines: bool + required for axis_geological """ - ax = plt.axes(polar=True) - - # Set upper and lower latitude limits (pole and lowlat) - if hemisphere == Hemisphere.North: - ax.set_ylim(90, lowlat) - ax.set_yticks(np.arange(lowlat, 90, 10)) - else: - # If hemisphere is South, lowlat must be negative - ax.set_ylim(-90, -abs(lowlat)) - ax.set_yticks(np.arange(-abs(lowlat), -90, -10)) - - # Locations of tick marks. Will be customisable in future - ax.set_xticks([0, np.radians(45), np.radians(90), np.radians(135), - np.radians(180), np.radians(225), np.radians(270), - np.radians(315)]) - - # Tick labels will depend on coordinate system - ax.set_xticklabels(['00', '', '06', '', '12', '', '18', '']) - ax.set_theta_zero_location("S") - + if ax is None: + ax = plt.axes(polar=True) + + # Set upper and lower latitude limits (pole and lowlat) + if hemisphere == Hemisphere.North: + ax.set_ylim(90, lowlat) + ax.set_yticks(np.arange(lowlat, 90, 10)) + else: + # If hemisphere is South, lowlat must be negative + ax.set_ylim(-90, -abs(lowlat)) + ax.set_yticks(np.arange(-abs(lowlat), -90, -10)) + + # Locations of tick marks. Will be customisable in future + ax.set_xticks([0, np.radians(45), np.radians(90), np.radians(135), + np.radians(180), np.radians(225), np.radians(270), + np.radians(315)]) + + # Tick labels will depend on coordinate system + ax.set_xticklabels(['00', '', '06', '', '12', '', '18', '']) + ax.set_theta_zero_location("S") + + if coastline is True: + if cartopyInstalled is False: + raise plot_exceptions.CartopyMissingError() + if cartopyVersion is False: + raise plot_exceptions.CartopyVersionError(cartopy.__version__) + # Read in the geometry object of the coastlines + cc = cfeature.NaturalEarthFeature('physical', 'coastline', '110m', + color='k', zorder=2.0) + # Convert geometry object coordinates to MLT + geom_mag = [] + for geom in cc.geometries(): + geom_mag.append(convert_geo_coastline_to_mag(geom, date)) + cc_mag = cfeature.ShapelyFeature(geom_mag, ccrs.PlateCarree(), + color='k', zorder=2.0) + # Plot each geometry object + for geom in cc_mag.geometries(): + plt.plot(*geom.coords.xy, color='k', linewidth=0.5, zorder=2.0) return ax, None def axis_geological(date, ax: object = None, hemisphere: Hemisphere = Hemisphere.North, - lowlat: int = 30, grid_lines: bool = True, **kwargs): + lowlat: int = 30, grid_lines: bool = True, + coastline: bool = False, **kwargs): """ + Plots a radar's Field Of View (FOV) fan plot for the given data and + scan number + + Parameters + ----------- + ax: matplotlib.pyplot axis + Pre-defined axis object to pass in, must be + geological projection + Default: Generates a geographical projection for the user + with geographic latitude/longitude labels + lowlat: int + Lower geographic latitude boundary for the geographic plot + Default: 30 + hemiphere: enum + Hemisphere of geographic projection. Can be Hemisphere.North or + Hemisphere.South for northern and southern hemispheres, + respectively + Default: Hemisphere.North + grid_lines: bool + add latitude/longtidue lines with labels to the plot + Default: True """ if cartopyInstalled is False: raise plot_exceptions.CartopyMissingError() @@ -96,18 +183,20 @@ def axis_geological(date, ax: object = None, pole_lat = -90 noon = 360 - deg_from_midnight ylocations = 5 + lowlat = -abs(lowlat) # handle none types or wrongly built axes proj = ccrs.Orthographic(noon, pole_lat) - ax = plt.subplot(111, projection=proj, aspect='auto') - if grid_lines: - ax.gridlines(draw_labels=True) - - extent = min(45e5, - (abs(proj.transform_point(noon, lowlat, - ccrs.PlateCarree()) - [1]))) - ax.set_extent(extents=(-extent, extent, -extent, extent), - crs=proj) + + if ax is None: + ax = plt.subplot(111, projection=proj, aspect='auto') + if grid_lines: + ax.gridlines(draw_labels=True) + + extent = abs(proj.transform_point(noon, lowlat, ccrs.PlateCarree())[1]) + ax.set_extent(extents=(-extent, extent, -extent, extent), crs=proj) + + if coastline is True: + ax.coastlines() return ax, ccrs diff --git a/pydarn/plotting/rtp.py b/pydarn/plotting/rtp.py index 7df83f4c5..cb6bdff7d 100644 --- a/pydarn/plotting/rtp.py +++ b/pydarn/plotting/rtp.py @@ -8,6 +8,7 @@ # 2021-06-18 Marina Schmidt (SuperDARN Canada) fixed ground scatter colour bug # 2021-07-06 Carley Martin added keyword to aid in rounding start times # 2022-03-04 Marina Schmidt added RangeEstimations in +# 2022-08-04 Carley Martin added elifs for HALF_SLANT options # # Disclaimer: # pyDARN is under the LGPL v3 license found in the root directory LICENSE.md @@ -486,26 +487,35 @@ def plot_range_time(cls, dmap_data: List[dict], parameter: str = 'v', # so the plots gets to the ends ax.margins(0) - # create color bar if True + # Create color bar if None supplied if not colorbar: with warnings.catch_warnings(): warnings.filterwarnings('error') try: if isinstance(norm, colors.LogNorm): - cb = ax.figure.colorbar(im, ax=ax, extend='both') + if zmin == 0: + cb = ax.figure.colorbar(im, ax=ax, extend='max') + else: + cb = ax.figure.colorbar(im, ax=ax, extend='both') else: locator = ticker.MaxNLocator(symmetric=True, min_n_ticks=3, integer=True, nbins='auto') ticks = locator.tick_values(vmin=zmin, vmax=zmax) - cb = ax.figure.colorbar(im, ax=ax, extend='both', - ticks=ticks) + if zmin == 0: + cb = ax.figure.colorbar(im, ax=ax, extend='max', + ticks=ticks) + else: + cb = ax.figure.colorbar(im, ax=ax, extend='both', + ticks=ticks) except (ZeroDivisionError, Warning): raise rtp_exceptions.RTPZeroError(parameter, beam_num, zmin, zmax, norm) from None + else: + cb = colorbar if colorbar_label != '': cb.set_label(colorbar_label) return im, cb, cmap, x, y, z_data @@ -1112,13 +1122,7 @@ def plot_summary(cls, dmap_data: List[dict], # plot range-time else: # Current standard is to only have groundscatter - # on the velocity plot. This may change in the future. - if range_estimation == RangeEstimation.SLANT_RANGE: - ymax = 3517.5 - elif range_estimation == RangeEstimation.GSMR: - ymax = 3517.5/2 - else: - ymax = 75 + # on the velocity plot. if groundscatter and axes_parameters[i] == 'v': grndflg = True else: @@ -1139,7 +1143,7 @@ def plot_summary(cls, dmap_data: List[dict], axes_parameters[i]][0], zmax=boundary_ranges[ axes_parameters[i]][1], - ymax=ymax, yspacing=500, + yspacing=500, background=background, range_estimation=range_estimation, **kwargs) @@ -1164,6 +1168,8 @@ def plot_summary(cls, dmap_data: List[dict], axes[i].set_ylabel('Slant Range (km)') elif range_estimation == RangeEstimation.GSMR: axes[i].set_ylabel('Ground Scatter\nMapped Range\n(km)') + elif range_estimation == RangeEstimation.HALF_SLANT: + axes[i].set_ylabel('Slant Range/2\n(km)') else: axes[i].set_ylabel('Range Gates') if i < num_plots-1: diff --git a/pydarn/utils/range_estimations.py b/pydarn/utils/range_estimations.py index 736d0ac62..d0a2aabb2 100644 --- a/pydarn/utils/range_estimations.py +++ b/pydarn/utils/range_estimations.py @@ -11,15 +11,33 @@ # supplemented by the additional permissions listed below. # # Modifications: +# 2022-08-04 CJM added HALF_SLANT option and gate2halfslant method # import enum import numpy as np -import math from pydarn import Re, C +def gate2halfslant(**kwargs): + """ + Calculate the slant range divided by 2 for each range gate + + Parameters + ---------- + None + + Returns + ------- + half_slant : np.array + returns an array of slant ranges divided by two for the radar + """ + slant_range = gate2slant(**kwargs) + half_slant = slant_range / 2 + return half_slant + + def gate2groundscatter(reflection_height: float = 250, **kwargs): """ Calculate the ground scatter mapped range (km) for each slanted range @@ -119,6 +137,7 @@ class RangeEstimation(enum.Enum): RANGE_GATE = enum.auto() SLANT_RANGE = (gate2slant,) + HALF_SLANT = (gate2halfslant,) GSMR = (gate2groundscatter,) # Need this to make the functions callable diff --git a/pydarn/utils/superdarn_radars.py b/pydarn/utils/superdarn_radars.py index 6e98e2360..a2f1c26d2 100644 --- a/pydarn/utils/superdarn_radars.py +++ b/pydarn/utils/superdarn_radars.py @@ -106,7 +106,7 @@ def read_hdw_file(abbrv, date: datetime = None, update: bool = False): # if the file does not exist then try # and download it if os.path.exists(hdw_file) is False: - get_hdw_files() + get_hdw_files(force=update) try: with open(hdw_file, 'r') as reader: lines = reader.readlines() diff --git a/pydarn/version.py b/pydarn/version.py index 4fc934222..4b2d39bb6 100644 --- a/pydarn/version.py +++ b/pydarn/version.py @@ -17,4 +17,4 @@ This file contains the version number of pydarn """ -__version__='3.0' +__version__='3.1' diff --git a/test/data/test.north.mp b/test/data/test.north.mp new file mode 100644 index 000000000..04bf2e5fc Binary files /dev/null and b/test/data/test.north.mp differ diff --git a/test/test_Fan.py b/test/test_Fan.py index 5999293d8..a8602ad97 100644 --- a/test/test_Fan.py +++ b/test/test_Fan.py @@ -40,7 +40,9 @@ def test_fov_series(self): @pytest.mark.parametrize('stid', [5, 97]) @pytest.mark.parametrize('ranges', [(5,70)]) -@pytest.mark.parametrize('boundary', [False, True]) +@pytest.mark.parametrize('boundary', [False]) +@pytest.mark.parametrize('rsep', [15]) +@pytest.mark.parametrize('frang', [90]) @pytest.mark.parametrize('fov_color', ['grey']) @pytest.mark.parametrize('alpha', [0.8, 1]) @pytest.mark.parametrize('radar_location', [False]) @@ -53,12 +55,13 @@ class TestFov: def test_fov_plot(self, stid, ranges, boundary, fov_color, alpha, radar_location, radar_label, - line_color, date, grid, line_alpha): + line_color, date, grid, line_alpha, rsep, + frang): """ this test will give bare minimum input needed for """ with warnings.catch_warnings(record=True): pydarn.Fan.plot_fov(stid=stid, ranges=ranges, boundary=boundary, fov_color=fov_color, - alpha=alpha, + alpha=alpha, rsep=rsep, frang=frang, radar_location=radar_location, radar_label=radar_label, grid=grid, line_color=line_color, date=date, @@ -85,5 +88,5 @@ def test_fan_plot(self, parameter, cmap, groundscatter, colorbar, with warnings.catch_warnings(record=True): pydarn.Fan.plot_fan(data, parameter=parameter, cmap=cmap, groundscatter=groundscatter, colorbar=colorbar, - colorbar_label=colorbar_label, zmin=zmin, zmax=zmax, - title=title, channel=channel) + colorbar_label=colorbar_label, zmin=zmin, + zmax=zmax, title=title, channel=channel) diff --git a/test/test_RTP.py b/test/test_RTP.py index 453ee5ae2..b67002dc2 100644 --- a/test/test_RTP.py +++ b/test/test_RTP.py @@ -51,7 +51,8 @@ def test_normal_summary_plot(self): @pytest.mark.parametrize('yspacing', [150, 250]) @pytest.mark.parametrize('range_estimation', [pydarn.RangeEstimation.RANGE_GATE, - pydarn.RangeEstimation.GSMR]) + pydarn.RangeEstimation.GSMR, + pydarn.RangeEstimation.HALF_SLANT]) @pytest.mark.parametrize('ymin', [10]) @pytest.mark.parametrize('ymax', [70]) @pytest.mark.parametrize('parameters', ['v', 'p_l', 'w_l']) @@ -59,12 +60,13 @@ def test_normal_summary_plot(self): @pytest.mark.parametrize('start_time', [dt.datetime(2018, 4, 4, 6, 2)]) @pytest.mark.parametrize('end_time', [dt.datetime(2018, 4, 4, 6, 4)]) @pytest.mark.parametrize('date_fmt', ['%H:%M']) +@pytest.mark.parametrize('round_start', [False]) class TestRangTime: def test_parameters_range_time(self, groundscatter_params, parameters, background, zmin, zmax, start_time, end_time, ymin, ymax, - colorbar_label, yspacing, + colorbar_label, yspacing, round_start, range_estimation, cmap, date_fmt): """ this test will give bare minimum input needed for """ with warnings.catch_warnings(record=True): @@ -76,6 +78,7 @@ def test_parameters_range_time(self, groundscatter_params, end_time=end_time, ymin=ymin, ymax=ymax, colorbar_label=colorbar_label, yspacing=yspacing, + round_start=round_start, range_estimation=range_estimation, cmap=cmap, date_fmt=date_fmt) plt.close('all') @@ -84,7 +87,7 @@ def test_parameters_channel_2_range_time(self, groundscatter_params, parameters, background, zmin, zmax, start_time, end_time, ymin, ymax, colorbar_label, - yspacing, + yspacing, round_start, range_estimation, cmap, date_fmt): """ this test will give bare minimum input needed for """ @@ -97,6 +100,7 @@ def test_parameters_channel_2_range_time(self, groundscatter_params, end_time=end_time, ymin=ymin, ymax=ymax, colorbar_label=colorbar_label, yspacing=yspacing, + round_start=round_start, range_estimation=range_estimation, cmap=cmap, date_fmt=date_fmt) plt.close('all') @@ -113,12 +117,13 @@ def test_parameters_channel_2_range_time(self, groundscatter_params, @pytest.mark.parametrize('start_time', [dt.datetime(2018, 4, 4, 6, 2)]) @pytest.mark.parametrize('end_time', [dt.datetime(2018, 4, 4, 6, 4)]) @pytest.mark.parametrize('date_fmt', ['%H:%M']) +@pytest.mark.parametrize('round_start', [False]) class TestTimeSeries: def test_parameters_time_series_channel2(self, parameters_scalar, gate, scale, cp_name, color, linestyle, linewidth, date_fmt, end_time, - start_time): + start_time, round_start): """ """ @@ -130,12 +135,13 @@ def test_parameters_time_series_channel2(self, parameters_scalar, gate, channel=2, scale=scale, cp_name=cp_name, color=color, linestyle=linestyle, - linewidth=linewidth) + linewidth=linewidth, + round_start=round_start,) def test_parameters_time_series_channel1(self, parameters_scalar, gate, scale, cp_name, color, linestyle, linewidth, date_fmt, end_time, - start_time): + start_time, round_start): """ """ @@ -147,7 +153,8 @@ def test_parameters_time_series_channel1(self, parameters_scalar, gate, channel=1, scale=scale, cp_name=cp_name, color=color, linestyle=linestyle, - linewidth=linewidth) + linewidth=linewidth, + round_start=round_start,) @pytest.mark.parametrize('fig_size', [(11, 10), (10, 11)]) @@ -166,7 +173,8 @@ def test_parameters_time_series_channel1(self, parameters_scalar, gate, @pytest.mark.parametrize('groundscatter', [False, 'grey']) @pytest.mark.parametrize('range_estimation', [pydarn.RangeEstimation.RANGE_GATE, - pydarn.RangeEstimation.GSMR]) + pydarn.RangeEstimation.GSMR, + pydarn.RangeEstimation.HALF_SLANT]) class TestSummaryPlots: def test_params_summary_plots(self, fig_size, watermark, boundary, cmaps, diff --git a/test/test_maps.py b/test/test_maps.py new file mode 100644 index 000000000..01382aee7 --- /dev/null +++ b/test/test_maps.py @@ -0,0 +1,63 @@ +# Copyright (C) 2020 SuperDARN Canada, University of Saskatchewan +# Author: Carley Martin +# +# Modifications: +# +# Disclaimer: +# pyDARN is under the LGPL v3 license found in the root directory LICENSE.md +# Everyone is permitted to copy and distribute verbatim copies of this license +# document, but changing it is not allowed. +# +# This version of the GNU Lesser General Public License incorporates the terms +# and conditions of version 3 of the GNU General Public License, +# supplemented by the additional permissions listed below. + +import datetime as dt +import matplotlib.pyplot as plt +import pytest +import warnings + +import pydarn + + +data = pydarn.SuperDARNRead('test/data/test.north.mp').read_map() + + +class TestMap_defaults: + + def test_map_defaults(self): + """ """ + with warnings.catch_warnings(record=True): + pydarn.Maps.plot_mapdata(data) + +@pytest.mark.parametrize('colorbar', [False]) +@pytest.mark.parametrize('colorbar_label', 'green') +@pytest.mark.parametrize('title', [False]) +@pytest.mark.parametrize('cmap', [plt.get_cmap('rainbow')]) +@pytest.mark.parametrize('time_delta', [2]) +@pytest.mark.parametrize('radar_location', [True]) +@pytest.mark.parametrize('zmin', [0]) +@pytest.mark.parametrize('zmax', [100]) +@pytest.mark.parametrize('len_factor', [150.0, 100.0]) +@pytest.mark.parametrize('color_vectors', [False]) +@pytest.mark.parametrize('hmb', [False]) +@pytest.mark.parametrize('map_info', [False]) +@pytest.mark.parametrize('imf_dial', [False]) +@pytest.mark.parametrize('reference_vector', [1000]) +class TestMap: + + def test_plot_map(self, colorbar, colorbar_label, + title, cmap, time_delta, radar_location, + zmin, zmax, len_factor, color_vectors, hmb, map_info, + imf_dial, reference_vector): + """ this test will give bare minimum input needed for """ + with warnings.catch_warnings(record=True): + pydarn.Maps.plot_mapdata(data, colorbar=colorbar, cmap=cmap, + colorbar_label=colorbar_label, title=title, + time_delta=time_delta, + radar_location=radar_location, zmin=zmin, + zmax=zmax, len_factor=len_factor, + color_vectors=color_vectors, hmb=hmb, + imf_dial=imf_dial, + reference_vector=reference_vector) + plt.close('all')