def plot_fan(cls, dmap_data: List[dict], ax=None, scan_index: int = 1, ranges: List = [0,75], boundary: bool = True, parameter: str = 'v', lowlat: int = 30, cmap: str = None, groundscatter: bool = False, zmin: int = None, zmax: int = None, colorbar: bool = True, colorbar_label: str = ''): """ Plots a radar's Field Of View (FOV) fan plot for the given data and scan number Parameters ----------- dmap_data: List[dict] Named list of dictionaries obtained from SDarn_read ax: matplotlib.pyplot axis Pre-defined axis object to pass in, must currently be polar projection Default: Generates a polar projection for the user with MLT/latitude labels scan_index: int Scan number from beginning of first record in file Default: 1 parameter: str Key name indicating which parameter to plot. Default: v (Velocity). Alternatives: 'p_l', 'w_l', 'elv' lowlat: int Lower AACGM latitude boundary for the polar plot Default: 50 ranges: list Set to a two element list of the lower and upper ranges to plot Default: [0,75] boundary: bool Set to false to not plot the outline of the FOV Default: True cmap: matplotlib.cm matplotlib colour map https://matplotlib.org/tutorials/colors/colormaps.html Default: Official pyDARN colour map for given parameter groundscatter : bool Set true to indicate if groundscatter should be plotted in grey Default: False zmin: int The minimum parameter value for coloring Default: {'p_l': [0], 'v': [-200], 'w_l': [0], 'elv': [0]} zmax: int The maximum parameter value for coloring Default: {'p_l': [50], 'v': [200], 'w_l': [250], 'elv': [50]} colorbar: bool Draw a colourbar if True Default: True colorbar_label: str the label that appears next to the colour bar. Requires colorbar to be true Default: '' Returns ----------- beam_corners_aacgm_lats n_beams x n_gates numpy array of AACGMv2 latitudes beam_corners_aacgm_lons n_beams x n_gates numpy array of AACGMv2 longitudes scan n_beams x n_gates numpy array of the scan data (for the selected parameter) grndsct n_beams x n_gates numpy array of the scan data (for the selected parameter) dtime datetime object for the scan plotted """ my_path = os.path.abspath(os.path.dirname(__file__)) base_path = os.path.join(my_path, '..') # Get scan numbers for each record beam_scan=build_scan(dmap_data) # Locate scan in loaded data plot_beams = np.where(beam_scan == scan_index) # Time for coordinate conversion dtime = dt.datetime(dmap_data[plot_beams[0][0]]['time.yr'], dmap_data[plot_beams[0][0]]['time.mo'], dmap_data[plot_beams[0][0]]['time.dy'], dmap_data[plot_beams[0][0]]['time.hr'], dmap_data[plot_beams[0][0]]['time.mt'], dmap_data[plot_beams[0][0]]['time.sc']) # Get radar beam/gate locations beam_corners_aacgm_lats, beam_corners_aacgm_lons=radar_fov(dmap_data[0]['stid'], coords='aacgm', date=dtime) fan_shape = beam_corners_aacgm_lons.shape # Work out shift due in MLT beam_corners_mlts = np.zeros((fan_shape[0], fan_shape[1])) mltshift = beam_corners_aacgm_lons[0, 0] - \ (aacgmv2.convert_mlt(beam_corners_aacgm_lons[0, 0], dtime) * 15) beam_corners_mlts = beam_corners_aacgm_lons - mltshift # Hold the beam positions thetas = np.radians(beam_corners_mlts) rs = beam_corners_aacgm_lats # Get range-gate data and groundscatter array for given scan scan = np.zeros((fan_shape[0] - 1, fan_shape[1]-1)) grndsct = np.zeros((fan_shape[0] - 1, fan_shape[1]-1)) #initialise arrays for i in np.nditer(plot_beams): try: slist = dmap_data[i.astype(int)]['slist'] #get a list of gates where there is data beam = dmap_data[i.astype(int)]['bmnum'] #get the beam number for the record scan[slist, beam] = dmap_data[i.astype(int)][parameter] grndsct[slist, beam] = dmap_data[i.astype(int)]['gflg'] # if there is no slist field this means partial record except KeyError: continue # Colour table and max value selection depending on parameter plotted # Load defaults if none given # TODO: use cmaps as over writting cmap is bad practice... # did I do that in my code ... hmm if cmap is None: cmap = {'p_l': 'plasma', 'v': PyDARNColormaps.PYDARN_VELOCITY, 'w_l': PyDARNColormaps.PYDARN_VIRIDIS, 'elv': PyDARNColormaps.PYDARN} cmap = plt.cm.get_cmap(cmap[parameter]) # Setting zmin and zmax defaultzminmax = {'p_l': [0, 50], 'v': [-200, 200], 'w_l': [0, 250], 'elv': [0, 50]} if zmin is None: zmin = defaultzminmax[parameter][0] if zmax is None: zmax = defaultzminmax[parameter][1] # Setup plot # This may screw up references if ax is None: ax = plt.axes(polar=True) if beam_corners_aacgm_lats[0,0] > 0: ax.set_ylim(90, lowlat) ax.set_yticks(np.arange(lowlat, 90, 10)) else: ax.set_ylim(-90, -abs(lowlat)) ax.set_yticks(np.arange(-abs(lowlat), -90, -10)) ax.set_xticklabels(['00', '', '06', '', '12', '', '18', '']) ax.set_theta_zero_location("S") # Begin plotting by iterating over ranges and beams for gates in range(ranges[0],ranges[1]-1): for beams in range(thetas.shape[1] - 2): # Index colour table correctly cmapindex = (scan[gates, beams] + abs(zmin)) /\ (abs(zmin) + abs(zmax)) if cmapindex < 0: cmapindex = 0 if cmapindex > 1: cmapindex = 1 colour_rgba = cmap(cmapindex) # Check for zero values (white) and groundscatter (gray) if scan[gates, beams] == 0: colour_rgba = 'w' if groundscatter and grndsct[gates, beams] == 1: colour_rgba = 'gray' #Angle for polar plotting theta = [thetas[gates, beams], thetas[gates + 1, beams], thetas[gates + 1, beams + 1], thetas[gates, beams + 1]] #Radius for polar plotting r = [rs[gates, beams], rs[gates + 1, beams], rs[gates + 1, beams + 1], rs[gates, beams + 1]] im = ax.fill(theta, r, color=colour_rgba) # Plot FOV outline if boundary is True: plt.polar(thetas[0:ranges[1], 0], rs[0:ranges[1], 0], color='black', linewidth=0.5) plt.polar(thetas[ranges[1] - 1, 0:thetas.shape[1] - 1], rs[ranges[1] - 1, 0:thetas.shape[1] - 1], color='black', linewidth=0.5) plt.polar(thetas[0:ranges[1], thetas.shape[1] - 2], rs[0:ranges[1], thetas.shape[1] - 2], color='black', linewidth=0.5) plt.polar(thetas[0, 0:thetas.shape[1] - 2], rs[0, 0:thetas.shape[1] - 2], color='black', linewidth=0.5) norm = colors.Normalize norm = norm(zmin, zmax) # Create color bar if True 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 colorbar_label != '': cb.set_label(colorbar_label) return beam_corners_aacgm_lats, beam_corners_aacgm_lons, scan, grndsct, dtime
def occ_gate_vs_time(station, year, month, day_range=None, hour_range=None, gate_range=None, beam_range=None, freq_range=None, time_units='mlt', plot_type='contour', local_testing=False): """ Produce a contour plot with gate on the y-axis and time along the x-axis. There are 2 subplots, one for ionospheric scatter and one for ground scatter Plots echo occurrence rate Notes: - This program was originally written to be run on maxwell.usask.ca. This decision was made because occurrence investigations often require chewing large amounts of data. - Only considers 45 km data - Does not distinguish frequency - This program uses fitACF 3.0 data. To change this, modify the source code. :param station: str: The radar station to consider, a 3 character string (e.g. "rkn"). For a complete listing of available stations, please see https://superdarn.ca/radar-info :param year: int: The year to consider :param month: int: The month to consider :param day_range: (<int>, <int>) (optional): Inclusive. The days of the month to consider. If omitted (or None), then all days will be considered. :param hour_range: (<int>, <int>) (optional): The hour range to consider. If omitted (or None), then all hours will be considered. Not quite inclusive: if you pass in (0, 5) you will get from 0:00-4:59 UT :param gate_range: (<int>, <int>) (optional): Inclusive. The gate range to consider. If omitted (or None), then all the gates will be considered. Note that gates start at 0, so gates (0, 3) is 4 gates. :param beam_range: (<int>, <int>) (optional): Inclusive. The beam range to consider. If omitted (or None), then all beams will be considered. Note that beams start at 0, so beams (0, 3) is 4 beams. :param freq_range: (<float>, <float>) (optional): Inclusive. The frequency range to consider in MHz. If omitted (or None), then all frequencies are considered. :param plot_type: str (optional): default is 'contour'. The type of plot, either 'contour' or 'pixel'. :param time_units: str: 'ut' for universal time or 'mlt' for magnetic local time: The time units to plot along x. :param local_testing: bool (optional): default is False. Set this to true if you are testing on your local machine. Program will then use local dummy data. :return: matplotlib.pyplot.figure The figure can then be modified, added to, printed out, or saved in whichever file format is desired. """ time_units = check_time_units(time_units) year = check_year(year) month = check_month(month) hour_range = check_hour_range(hour_range) all_radars_info = SuperDARNRadars() this_radars_info = all_radars_info.radars[pydarn.read_hdw_file( station).stid] # Grab radar info radar_id = this_radars_info.hardware_info.stid gate_range = check_gate_range(gate_range, this_radars_info.hardware_info) beam_range = check_beam_range(beam_range, this_radars_info.hardware_info) print("Retrieving data...") df = get_data_handler(station, year_range=(year, year), month_range=(month, month), hour_range=hour_range, day_range=day_range, gate_range=gate_range, beam_range=beam_range, freq_range=freq_range, occ_data=True, local_testing=local_testing) df = only_keep_45km_res_data(df) # Get our raw x-data if time_units == "mlt": print("Computing MLTs for " + str(year) + " data...") # To compute mlt we need longitudes.. use the middle of the month as magnetic field estimate date_time_est, _ = build_datetime_epoch(year, month, 15, 0) cell_corners_aacgm_lats, cell_corners_aacgm_lons = \ radar_fov(stid=radar_id, coords='aacgm', date=date_time_est) df = add_mlt_to_df(cell_corners_aacgm_lons=cell_corners_aacgm_lons, cell_corners_aacgm_lats=cell_corners_aacgm_lats, df=df) df['xdata'] = df['mlt'] else: print("Computing UTs for " + str(year) + " data...") ut_time = [] for i in range(len(df)): ut_time_here = df['datetime'].iat[i].hour + df['datetime'].iat[i].minute / 60 + \ df['datetime'].iat[i].second / 3600 if ut_time_here > 24: ut_time_here = ut_time_here - 24 elif ut_time_here < 0: ut_time_here = ut_time_here + 24 ut_time.append(ut_time_here) df['xdata'] = np.asarray(ut_time) df = df.loc[(df['xdata'] >= hour_range[0]) & (df['xdata'] <= hour_range[1])] df.reset_index(drop=True, inplace=True) print("Preparing the plot...") # Setup the plot fig, ax = plt.subplots(figsize=[10, 8], dpi=300, nrows=2, ncols=1) plt.subplots_adjust(hspace=0.3, left=0.1, right=1) fig.suptitle(calendar.month_name[month] + " " + str(year) + " at " + station.upper() + "; Beams " + str(beam_range[0]) + "-" + str(beam_range[1]) + "; Frequencies " + str(freq_range[0]) + "-" + str(freq_range[1]) + " MHz", fontsize=18) ax[0].set_title("Ionospheric Scatter", fontsize=16) ax[1].set_title("Ground Scatter", fontsize=16) # Apply common subplot formatting for i in range(ax.size): ax[i].set_ylim(gate_range) ax[i].set_xlim(hour_range) ax[i].xaxis.set_major_locator(MultipleLocator(4)) ax[i].tick_params(axis='both', which='major', direction='in', color='white') ax[i].grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5, zorder=4, color='white') plt.xticks(fontsize=14) plt.yticks(fontsize=14) ax[i].set_ylabel("Range Gate", fontsize=16) ax[i].set_xlabel("Time, " + time_units.upper(), fontsize=16) # Compute hour_edges bins_per_hour = 4 n_bins_x = (hour_range[1] - hour_range[0]) * bins_per_hour # quarter hour bins delta_hour = (hour_range[1] - hour_range[0]) / n_bins_x hour_edges = np.linspace(hour_range[0], hour_range[1], num=(n_bins_x + 1)) # Compute gate_edges n_bins_y = ((gate_range[1] + 1) - gate_range[0]) # Single gate bins gate_edges = np.linspace(gate_range[0], gate_range[1] + 1, num=(n_bins_y + 1), dtype=int) print("Computing binned occ rates...") contour_data_is = np.empty(shape=(n_bins_x, n_bins_y)) contour_data_is[:] = math.nan contour_data_gs = np.empty(shape=(n_bins_x, n_bins_y)) contour_data_gs[:] = math.nan for hour_idx, hour_start in enumerate(hour_edges): if hour_start == hour_edges[-1]: continue # The last edge is not a starting hour hour_end = hour_start + delta_hour df_hh = df[(df['xdata'] >= hour_start) & (df['xdata'] <= hour_end)] for gate in gate_edges: if gate == gate_edges[-1]: continue # The last edge is not a starting gate df_hh_gg = df_hh[df_hh['slist'] == gate] try: contour_data_is[hour_idx][gate] = sum( df_hh_gg['good_iono_echo']) / len(df_hh_gg) contour_data_gs[hour_idx][gate] = sum( df_hh_gg['good_grndscat_echo']) / len(df_hh_gg) except ZeroDivisionError: # There are no points in this interval contour_data_is[hour_idx, gate] = math.nan contour_data_gs[hour_idx, gate] = math.nan except BaseException as e: print("Hour index: " + str(hour_idx)) print("Gate: " + str(gate)) raise e # Compute bin centers bin_xwidth = (hour_edges[1] - hour_edges[0]) bin_ywidth = (gate_edges[1] - gate_edges[0]) bin_xcenters = hour_edges[1:] - bin_xwidth / 2 bin_ycenters = gate_edges[1:] - bin_ywidth / 2 # Plot the data levels = 12 levels = np.linspace(start=0, stop=1, num=(levels + 1)) if plot_type == "contour": plot0 = ax[0].contourf(bin_xcenters, bin_ycenters, contour_data_is.transpose(), cmap='jet', levels=levels) plot1 = ax[1].contourf(bin_xcenters, bin_ycenters, contour_data_gs.transpose(), cmap='jet', levels=levels) elif plot_type == "pixel": plot0 = ax[0].imshow(np.flip(contour_data_is.transpose(), axis=0), aspect='auto', cmap="jet", extent=(hour_range[0], hour_range[1], gate_range[0], gate_range[1] + 1), vmin=0, vmax=1) plot1 = ax[1].imshow(np.flip(contour_data_gs.transpose(), axis=0), aspect='auto', cmap="jet", extent=(hour_range[0], hour_range[1], gate_range[0], gate_range[1] + 1), vmin=0, vmax=1) else: raise Exception("plot_type not recognized") cbar0 = fig.colorbar(plot0, ax=ax[0], shrink=0.75, format='%.2f') cbar0.ax.tick_params(labelsize=14) cbar1 = fig.colorbar(plot1, ax=ax[1], shrink=0.75, format='%.2f') cbar1.ax.tick_params(labelsize=14) print("Returning the is and gs figures...") return df, fig
'ro', markersize=radar_marker_size, transform=ccrs.Geodetic(), label="RISR", zorder=3) date_time = datetime.utcnow() ax.add_feature(Nightshade(date_time, alpha=0.25)) # ax.set_title("Night time shading for " + str(date_time) + " UT") ax.set_title("One-and-one-half-hop Geometry Check") # Try to plot RKN's FoV # RKN is id 65 ranges = [0, 100] rkn_beam_corners_lats, rkn_beam_corners_lons = radar_fov(65, coords='geo') rkn_fan_shape = rkn_beam_corners_lons.shape inv_beam_corners_lats, inv_beam_corners_lons = radar_fov(64, coords='geo') inv_fan_shape = inv_beam_corners_lons.shape # plot all the RKN beam boundary lines for beam_line in range(rkn_fan_shape[1]): plt.plot(rkn_beam_corners_lons[0:ranges[1] + 1, beam_line], rkn_beam_corners_lats[0:ranges[1] + 1, beam_line], color='black', linewidth=0.1, transform=ccrs.Geodetic()) # # left boundary line # plt.plot(rkn_beam_corners_lons[0:ranges[1] + 1, 0], rkn_beam_corners_lats[0:ranges[1] + 1, 0],
ax.text(text_offset_multiplier * ax.get_xlim()[1], 0, "06", ha='left', va='center') ax.text(text_offset_multiplier * ax.get_xlim()[0], 0, "18", ha='right', va='center') beam_range = (0, 15) gate_range = (0, 74) cell_corners_lats, cell_corners_lons = radar_fov(stid=radar_id, coords='geo', date=date) # plot all the beam boundary lines for beam_line in range(beam_range[0], beam_range[1] + 2): plt.plot(cell_corners_lons[gate_range[0]:gate_range[1] + 2, beam_line], cell_corners_lats[gate_range[0]:gate_range[1] + 2, beam_line], color='black', linewidth=0.1, transform=ccrs.Geodetic(), zorder=4) # plot the arcs boundary lines for range_ in range(gate_range[0], gate_range[1] + 2): plt.plot(cell_corners_lons[range_, beam_range[0]:beam_range[1] + 2], cell_corners_lats[range_, beam_range[0]:beam_range[1] + 2],
def occ_full_circle(station, year, month_range=None, day_range=None, gate_range=None, beam_range=None, time_units='mlt', plot_type='contour', local_testing=False): """ Produce a full circle stereographic plot in either ut or mlt (12 at the top). Can plot a simple echo count, ground scatter count, or average a fitACF parameter over the provided time range. Notes: - This program was originally written to be run on maxwell.usask.ca. This decision was made because occurrence investigations often require chewing large amounts of data. - Does not distinguish frequency - Only considers 45 km data. (a warning will be printed if other spatial resolution data is stripped from the dataset) - This program uses fitACF 3.0 data. To change this, modify the source code. :param station: str: The radar station to consider, a 3 character string (e.g. "rkn"). For a complete listing of available stations, please see https://superdarn.ca/radar-info :param year: int: The year to consider. :param month_range: (<int>, <int>) (optional): Inclusive. The months of the year to consider. If omitted (or None), then all days will be considered. :param day_range: (<int>, <int>) (optional): Inclusive. The days of the month to consider. If omitted (or None), then all days will be considered. :param gate_range: (<int>, <int>) (optional): Inclusive. The gate range to consider. If omitted (or None), then all the gates will be considered. Note that gates start at 0, so gates (0, 3) is 4 gates. :param beam_range: (<int>, <int>) (optional): Inclusive. The beam range to consider. If omitted (or None), then all beams will be considered. Note that beams start at 0, so beams (0, 3) is 4 beams. :param time_units: str: 'ut' for universal time or 'mlt' for magnetic local time: The time units to plot on the circle, 12 is always at the top. Default is 'mlt' :param plot_type: str (optional): The type of plot, either 'contour' or 'pixel', default is 'contour' :param local_testing: bool (optional): Set this to true if you are testing on your local machine. Program will then use local dummy data. :return: matplotlib.pyplot.figure: The figure. It can then be modified, added to, printed out, or saved in whichever file format is desired. """ time_units = check_time_units(time_units) year = check_year(year) all_radars_info = SuperDARNRadars() this_radars_info = all_radars_info.radars[pydarn.read_hdw_file( station).stid] # Grab radar info radar_id = this_radars_info.hardware_info.stid hemisphere = this_radars_info.hemisphere radar_lon = this_radars_info.hardware_info.geographic.lon radar_lat = this_radars_info.hardware_info.geographic.lat gate_range = check_gate_range(gate_range, this_radars_info.hardware_info) beam_range = check_beam_range(beam_range, this_radars_info.hardware_info) print("Retrieving data...") df = get_data_handler(station, year_range=(year, year), month_range=month_range, day_range=day_range, gate_range=gate_range, beam_range=beam_range, occ_data=True, local_testing=local_testing) df = only_keep_45km_res_data(df) # To compute mlt we need longitudes.. use the middle of the month as magnetic field estimate date_time_est, _ = build_datetime_epoch(year, 6, 15, 0) print("Computing MLTs for " + str(year) + " data...") cell_corners_aacgm_lats, cell_corners_aacgm_lons = radar_fov( stid=radar_id, coords='aacgm', date=date_time_est) df = add_mlt_to_df(cell_corners_aacgm_lons=cell_corners_aacgm_lons, cell_corners_aacgm_lats=cell_corners_aacgm_lats, df=df) # Get our raw x-data if time_units == "mlt": df['xdata'] = df['mlt'] else: print("Computing UTs for " + str(year) + " data...") ut_time = [] for i in range(len(df)): ut_time_here = df['datetime'].iat[i].hour + df['datetime'].iat[i].minute / 60 + \ df['datetime'].iat[i].second / 3600 if ut_time_here > 24: ut_time_here = ut_time_here - 24 elif ut_time_here < 0: ut_time_here = ut_time_here + 24 ut_time.append(ut_time_here) df['xdata'] = np.asarray(ut_time) print("Preparing the plot...") fig = plt.figure(figsize=(5, 5), dpi=300) lat_extreme = 40 * hemisphere.value # deg if hemisphere.value == 1: ax = fig.add_subplot(1, 1, 1, projection=ccrs.NorthPolarStereo()) elif hemisphere.value == -1: ax = fig.add_subplot(1, 1, 1, projection=ccrs.SouthPolarStereo()) else: raise Exception("hemisphere not recognized") ax.set_extent([-180, 180, hemisphere.value * 90, lat_extreme], crs=ccrs.PlateCarree()) # Compute a circle in axis coordinates which can be used as a boundary theta = np.linspace(0, 2 * np.pi, 100) center, radius = [0.5, 0.5], 0.5 vertices = np.vstack([np.sin(theta), np.cos(theta)]).T circle = mpath.Path(vertices * radius + center) ax.set_boundary(circle, transform=ax.transAxes) # Add gridlines and mlt labels text_offset_multiplier = 1.03 gl = ax.gridlines(draw_labels=True, linestyle='--', zorder=5) gl.xlocator = mticker.FixedLocator([-180, -135, -90, -45, 0, 45, 90, 135]) gl.xformatter = LONGITUDE_FORMATTER gl.yformatter = LATITUDE_FORMATTER # Print clock numbers ax.text(0, text_offset_multiplier * ax.get_ylim()[1], "12", ha='center', va='bottom') ax.text(0, text_offset_multiplier * ax.get_ylim()[0], "00", ha='center', va='top') ax.text(text_offset_multiplier * ax.get_xlim()[1], 0, "06", ha='left', va='center') ax.text(text_offset_multiplier * ax.get_xlim()[0], 0, "18", ha='right', va='center') # Print time units ax.text(text_offset_multiplier * ax.get_xlim()[0], text_offset_multiplier * ax.get_ylim()[1], time_units.upper(), ha='left', va='bottom') # Convert radar coordinates to aacgm and plot radar track radar_lat_aacgm, radar_lon_aacgm, radar_mlt = get_aacgm_coord( radar_lat, radar_lon, 0, date_time_est) radar_mlts = np.arange(0, 360, 1) radar_lats_aacgm = np.asarray([radar_lat_aacgm] * len(radar_mlts)) ax.plot(radar_mlts, radar_lats_aacgm, color='k', linewidth=0.5, linestyle="--", transform=ccrs.Geodetic(), label="Radar Path") # Right now xdata is in the range 0-24, we need to put it in the range 0-360 for circular plotting df['xdata'] = 15 * df['xdata'] print("Computing binned occ rates...") # Compute mlt edges deg_mlt_per_bin = 2 n_bins_mlt = int(360 / deg_mlt_per_bin) mlt_edges = np.linspace(0, 360, num=(n_bins_mlt + 1)) delta_mlt = mlt_edges[1] - mlt_edges[0] # Compute latitude edges n_bins_lat = 90 - abs(lat_extreme) # One bin per degree of latitude lat_edges = np.linspace(lat_extreme, 90, num=(n_bins_lat + 1)) delta_lat = lat_edges[1] - lat_edges[0] contour_data = np.empty(shape=(n_bins_mlt, n_bins_lat)) contour_data[:] = math.nan for mlt_idx, start_mlt in enumerate(mlt_edges): if start_mlt == mlt_edges[-1]: continue # The last edge is not a start end_mlt = start_mlt + delta_mlt df_mlt = df[(df['xdata'] >= start_mlt) & (df['xdata'] <= end_mlt)] for lat_idx, start_lat in enumerate(lat_edges): if start_lat == lat_edges[-1]: continue # The last edge is not a start end_lat = start_lat + delta_lat df_mlt_lat = df_mlt[(df_mlt['lat'] >= start_lat) & (df_mlt['lat'] <= end_lat)] try: contour_data[mlt_idx][lat_idx] = sum( df_mlt_lat['good_echo']) / len(df_mlt_lat) except ZeroDivisionError: # There are no point in this interval contour_data[mlt_idx, lat_idx] = math.nan except BaseException as e: print("MLT index: " + str(mlt_idx)) print("LAT index: " + str(lat_idx)) raise e contour_range = [[0, 360], [hemisphere.value * 37, hemisphere.value * 90]] # Compute bin centers bin_xwidth = (mlt_edges[1] - mlt_edges[0]) bin_ywidth = (lat_edges[1] - lat_edges[0]) bin_xcenters = mlt_edges[1:] - bin_xwidth / 2 bin_ycenters = lat_edges[1:] - bin_ywidth / 2 levels = 12 levels = np.linspace(start=0, stop=1, num=(levels + 1)) if plot_type == "contour": plot = ax.contourf(bin_xcenters, bin_ycenters, contour_data.transpose(), cmap='jet', levels=levels, transform=ccrs.PlateCarree()) elif plot_type == "pixel": plot = ax.imshow(np.flip(contour_data.transpose(), axis=0), aspect='auto', cmap="jet", transform=ccrs.PlateCarree()) else: raise Exception("plot_type not recognized") cbar = fig.colorbar(plot, ax=ax, shrink=0.75, orientation="horizontal", format='%.2f') cbar.ax.tick_params(labelsize=14, labelrotation=30) return df, fig
def occ_year_vs_ut(station, year_range, month_range=None, time_units='mlt', hour_range=None, gate_range=None, beam_range=None, parameter=None, local_testing=False): """ Produce a contour plot with year on the y-axis and time along the x-axis. Plots a simple echo count Notes: - This program was originally written to be run on maxwell.usask.ca. This decision was made because occurrence investigations often require chewing large amounts of data. - Only considers 45 km data - Does not distinguish frequency - This program uses fitACF 3.0 data. To change this, modify the source code. - year_range is assumed UT, hour_range is either MLT of UT depending on time_units :param station: str: The radar station to consider, a 3 character string (e.g. "rkn"). For a complete listing of available stations, please see https://superdarn.ca/radar-info :param year_range: (<int>, <int>): Inclusive. The year range to consider. :param month_range: (<int>, <int>) (optional): Inclusive. The months of the year to consider. If omitted (or None), then all days will be considered. :param time_units: str: 'ut' for universal time or 'mlt' for magnetic local time: The time units to plot along x. Default is 'mlt' :param hour_range: (<int>, <int>) (optional): The hour range to consider. If omitted (or None), then all hours will be considered. Not quite inclusive: if you pass in (0, 5) you will get from 0:00-4:59 UT :param gate_range: (<int>, <int>) (optional): Inclusive. The gate range to consider. If omitted (or None), then all the gates will be considered. Note that gates start at 0, so gates (0, 3) is 4 gates. :param beam_range: (<int>, <int>) (optional): Inclusive. The beam range to consider. If omitted (or None), then all beams will be considered. Note that beams start at 0, so beams (0, 3) is 4 beams. :param parameter: str (optional): Parameter to be averaged (e.g. 'v' or 'p_l') If omitted, then a simple echo count will be plotted. :param local_testing: bool (optional): Set this to true if you are testing on your local machine. Program will then use local dummy data. :return: matplotlib.pyplot.figure The figure can then be modified, added to, printed out, or saved in whichever file format is desired. """ time_units = check_time_units(time_units) hour_range = check_hour_range(hour_range) print("Retrieving data...") df = get_data_handler(station, year_range=year_range, month_range=month_range, day_range=(1, 31), hour_range=hour_range, gate_range=gate_range, beam_range=beam_range, local_testing=local_testing) all_radars_info = SuperDARNRadars() this_radars_info = all_radars_info.radars[pydarn.read_hdw_file(station).stid] # Grab radar info radar_id = this_radars_info.hardware_info.stid print("Filtering data...") df = df.loc[(df['p_l'] >= 3)] # Restrict to points with at least 3 dB if parameter is not None: zmin, zmax = z_min_max_defaults(parameter) df = df.loc[(df[parameter] >= zmin) & (df[parameter] <= zmax)] if parameter == 'v': cmap = 'seismic_r' levels = np.linspace(zmin, zmax, 13, endpoint=True) else: levels = 6 cmap = modified_viridis(levels=levels) else: levels = 12 cmap = modified_viridis(levels=levels) df.reset_index(drop=True, inplace=True) df = only_keep_45km_res_data(df) print("Preparing the plot...") n_rows = (year_range[1] + 1) - year_range[0] # We will have one subplot for every year of observations fig, ax = plt.subplots(figsize=(8, 9), sharex='col', dpi=300, nrows=n_rows, ncols=1) plt.subplots_adjust(hspace=0.05) # Apply common subplot formatting ax[n_rows - 1].set_xlabel("Time, " + time_units.upper(), fontsize=18) for row in reversed(range(n_rows)): ax[row].set_ylim([0, 12]) ax[row].set_xlim(hour_range) ax[row].tick_params(axis='y', which='major', labelleft=False, direction='in') ax[row].tick_params(axis='x', which='major', direction='in') plt.xticks(fontsize=16) ax[row].xaxis.set_major_locator(MultipleLocator(2)) # Every 2 hours ax[row].yaxis.set_major_locator(MultipleLocator(6.5)) # Half way through the year ax[row].grid(b=True, which='major', axis='x', linestyle='--', linewidth=0.5, zorder=4) ax[row].grid(b=True, which='major', axis='y', linestyle='--', linewidth=0.5, zorder=4) ax[row].set_ylabel(year_range[1] - row, fontsize=18) # Loop through the years, adding in the data n_bins_y = 48 # half month bins n_bins_x = (hour_range[1] - hour_range[0]) * 2 # half hour bins contour_range = [ax[0].get_xlim(), ax[0].get_ylim()] # All plots are the same size for row in reversed(range(n_rows)): year = year_range[1] - row # Build a restricted dataframe with the year's data start_datetime, start_epoch = build_datetime_epoch(year=year, month=1, day=1, hour=0) end_datetime, end_epoch = build_datetime_epoch(year=year, month=12, day=31, hour=24) df_yy = df.loc[(df['epoch'] >= start_epoch) & (df['epoch'] <= end_epoch)].copy() df_yy.reset_index(drop=True, inplace=True) df_yy['ut_time'] = df_yy['hour'] + df_yy['minute'] / 60 + df_yy['second'] / 3600 if time_units == "mlt": print("Computing MLTs for " + str(year) + " data...") # To compute mlt we need longitudes.. # we will use the middle of the year and assume magnetic longitudes don't change much over the year mid_datetime = start_datetime + (end_datetime - start_datetime) / 2 cell_corners_aacgm_lats, cell_corners_aacgm_lons = \ radar_fov(stid=radar_id, coords='aacgm', date=mid_datetime) df_yy = add_mlt_to_df(cell_corners_aacgm_lons=cell_corners_aacgm_lons, cell_corners_aacgm_lats=cell_corners_aacgm_lats, df=df_yy) df_yy['xdata'] = df_yy['mlt'] else: df_yy['xdata'] = df_yy['ut_time'] df_yy = df_yy.loc[(df_yy['xdata'] >= hour_range[0]) & (df_yy['xdata'] <= hour_range[1])] df_yy.reset_index(drop=True, inplace=True) # Compute decimal datetime to plot along y df_yy['ydata'] = (df_yy['month'] - 1) + (df_yy['day'] - 1) / 31 + df_yy['ut_time'] / 730 if parameter is None: # We just want a simple echo count binned_counts, bin_xedges, bin_yedges, bin_numbers = stats.binned_statistic_2d( df_yy['xdata'], df_yy['ydata'], values=None, statistic='count', bins=[n_bins_x, n_bins_y], range=contour_range) else: # We want to the median of the chosen parameter binned_counts, bin_xedges, bin_yedges, bin_numbers = stats.binned_statistic_2d( df_yy['xdata'], df_yy['ydata'], values=df_yy[parameter], statistic='median', bins=[n_bins_x, n_bins_y], range=contour_range) binned_counts = np.nan_to_num(binned_counts) # Compute bin centers bin_xwidth = (bin_xedges[1] - bin_xedges[0]) bin_ywidth = (bin_yedges[1] - bin_yedges[0]) bin_xcenters = bin_xedges[1:] - bin_xwidth / 2 bin_ycenters = bin_yedges[1:] - bin_ywidth / 2 # Plot the data cont = ax[row].contourf(bin_xcenters, bin_ycenters, binned_counts.transpose(), cmap=cmap, levels=levels) cbar = fig.colorbar(cont, ax=ax[row], shrink=0.75) cbar.ax.tick_params(labelsize=16) print("Returning the figure...") return fig
def occ_fan(station, year_range, month_range=None, day_range=None, hour_range=None, gate_range=None, beam_range=None, local_testing=False, parameter=None, plot_ground_scat=False): """ Produce a fan plot. Can plot a simple echo count, ground scatter count, or average a fitACF parameter over the provided time range. Notes: - This program was originally written to be run on maxwell.usask.ca. This decision was made because occurrence investigations often require chewing large amounts of data. - Does not distinguish frequency - Only considers 45 km data. (a warning will be printed if other spatial resolution data is stripped from the dataset) - This program uses fitACF 3.0 data. To change this, modify the source code. - All times and dates are assumed UT :param station: str: The radar station to consider, a 3 character string (e.g. "rkn"). For a complete listing of available stations, please see https://superdarn.ca/radar-info :param year_range: (<int>, <int>): Inclusive. The year range to consider. :param month_range: (<int>, <int>) (optional): Inclusive. The months of the year to consider. If omitted (or None), then all days will be considered. :param day_range: (<int>, <int>) (optional): Inclusive. The days of the month to consider. If omitted (or None), then all days will be considered. :param hour_range: (<int>, <int>) (optional): The hour range to consider. If omitted (or None), then all hours will be considered. Not inclusive: if you pass in (0, 5) you will get from 0:00-4:59 UT :param gate_range: (<int>, <int>) (optional): Inclusive. The gate range to consider. If omitted (or None), then all the gates will be considered. Note that gates start at 0, so gates (0, 3) is 4 gates. :param beam_range: (<int>, <int>) (optional): Inclusive. The beam range to consider. If omitted (or None), then all beams will be considered. Note that beams start at 0, so beams (0, 3) is 4 beams. :param local_testing: bool (optional): Set this to true if you are testing on your local machine. Program will then use local dummy data. :param parameter: str (optional): Parameter to be averaged (e.g. 'v' or 'p_l') If omitted, then a simple echo count will be plotted. :param plot_ground_scat: bool (optional) Set this to true if you would like to plot ground scatter counts. Default is False If plot_ground_scat is set to True, then :param parameter is ignored. :return: matplotlib.pyplot.figure, 2d np.array: The figure and the scan data plotted. The figure can then be modified, added to, printed out, or saved in whichever file format is desired. """ print("Retrieving data...") df = get_data_handler(station, year_range=year_range, month_range=month_range, day_range=day_range, hour_range=hour_range, gate_range=gate_range, beam_range=beam_range, local_testing=local_testing) print("Getting some hardware info...") all_radars_info = SuperDARNRadars() this_radars_info = all_radars_info.radars[pydarn.read_hdw_file( station).stid] # Grab radar info hemisphere = this_radars_info.hemisphere radar_lon = this_radars_info.hardware_info.geographic.lon radar_lat = this_radars_info.hardware_info.geographic.lat radar_id = this_radars_info.hardware_info.stid print("Filtering data...") df = df.loc[(df['p_l'] >= 3)] # Restrict to points with at least 3 dB if not plot_ground_scat and parameter is not None: zmin, zmax = z_min_max_defaults(parameter) df = df.loc[(df[parameter] >= zmin) & (df[parameter] <= zmax)] df.reset_index(drop=True, inplace=True) df = only_keep_45km_res_data(df) print("Preparing the plot...") # Prepare the figure fig = plt.figure(figsize=(5, 5), dpi=300) if hemisphere.value == 1: # Northern hemisphere min_lat = 37 # deg ax = fig.add_subplot(1, 1, 1, projection=ccrs.NorthPolarStereo()) ax.set_extent([-180, 180, 90, min_lat], crs=ccrs.PlateCarree()) elif hemisphere.value == -1: # Southern hemisphere max_lat = -34 # deg ax = fig.add_subplot(1, 1, 1, projection=ccrs.SouthPolarStereo()) ax.set_extent([-180, 180, -90, max_lat], crs=ccrs.PlateCarree()) else: raise Exception("Error: hemisphere not recognized") ax.gridlines() ax.add_feature(cfeature.OCEAN) ax.add_feature(cfeature.LAND) # Compute a circle in axis coordinates which can be used as a boundary theta = np.linspace(0, 2 * np.pi, 100) center, radius = [0.5, 0.5], 0.5 vertices = np.vstack([np.sin(theta), np.cos(theta)]).T circle = mpath.Path(vertices * radius + center) ax.set_boundary(circle, transform=ax.transAxes) # Plot the radar as a red dot plt.plot([radar_lon, radar_lon], [radar_lat, radar_lat], 'ro', markersize=1, transform=ccrs.Geodetic(), label=this_radars_info.name) cell_corners_lats, cell_corners_lons = radar_fov(stid=radar_id, coords='geo') print("Computing scan...") num_gates = (gate_range[1] + 1) - gate_range[0] num_beams = (beam_range[1] + 1) - beam_range[0] # Loop through all the gate/beam cells and build the scans # First index will be gates, the second will be beams scans = np.zeros((num_gates, num_beams)) grndsct_scans = np.zeros((num_gates, num_beams)) for gate_idx in range(num_gates): for beam_idx in range(num_beams): gate = gate_range[0] + gate_idx beam = beam_range[0] + beam_idx # print("Gate: " + str(gate) + ", beam : " + str(beam)) cell_df = df[(df['slist'] == gate) & (df['bmnum'] == beam)] grndsct_scans[gate_idx, beam_idx] = cell_df[(cell_df['gflg'] == 1)].shape[0] if parameter is None: # We want a simple echo count scans[gate_idx, beam_idx] = cell_df[(cell_df['gflg'] == 0)].shape[0] else: # Otherwise average the provided parameter try: scans[gate_idx, beam_idx] = statistics.median( cell_df.query('gflg == 0')[parameter]) except statistics.StatisticsError: # We can't take a median because there are no points scans[gate_idx, beam_idx] = math.nan # Build reduced arrays containing only the cells in the specified gate/beam range reduced_beam_corners_lons = cell_corners_lons[gate_range[0]:gate_range[1] + 2, beam_range[0]:beam_range[1] + 2] reduced_beam_corners_lats = cell_corners_lats[gate_range[0]:gate_range[1] + 2, beam_range[0]:beam_range[1] + 2] print("Plotting Data...") if plot_ground_scat: cmap = modified_viridis() data = ax.pcolormesh(reduced_beam_corners_lons, reduced_beam_corners_lats, grndsct_scans, transform=ccrs.PlateCarree(), cmap=cmap, zorder=3) else: if parameter == 'v': cmap = 'seismic_r' else: cmap = modified_viridis() data = ax.pcolormesh(reduced_beam_corners_lons, reduced_beam_corners_lats, scans, transform=ccrs.PlateCarree(), cmap=cmap, zorder=3) fig.colorbar(data, ax=ax) # plot all the beam boundary lines for beam_line in range(beam_range[0], beam_range[1] + 2): plt.plot(cell_corners_lons[gate_range[0]:gate_range[1] + 2, beam_line], cell_corners_lats[gate_range[0]:gate_range[1] + 2, beam_line], color='black', linewidth=0.1, transform=ccrs.Geodetic(), zorder=4) # plot the arcs boundary lines for range_ in range(gate_range[0], gate_range[1] + 2): plt.plot(cell_corners_lons[range_, beam_range[0]:beam_range[1] + 2], cell_corners_lats[range_, beam_range[0]:beam_range[1] + 2], color='black', linewidth=0.1, transform=ccrs.Geodetic(), zorder=4) print("Returning the figure and scan...") if plot_ground_scat: return fig, grndsct_scans else: return fig, scans
# Prepare the figure min_lat = 37 # deg fig = plt.figure(figsize=(5, 5), dpi=600) ax = fig.add_subplot(1, 1, 1, projection=ccrs.NorthPolarStereo()) ax.set_extent([-180, 180, 90, min_lat], crs=ccrs.PlateCarree()) # ax.gridlines() # ax.stock_img() # ax.add_feature(cfeature.COASTLINE) # ax.add_feature(cfeature.BORDERS, linestyle="-", linewidth=0.5) # ax.add_feature(cfeature.OCEAN) # ax.add_feature(cfeature.LAND) beam_corners_aacgm_lats, beam_corners_aacgm_lons = \ radar_fov(stid=hdw_info.stid, coords='aacgm', date=date) fan_shape = beam_corners_aacgm_lons.shape """ Shift the whole fan - like in pydarn """ # beam_corners_mlts = np.zeros((fan_shape[0], fan_shape[1])) mltshift = beam_corners_aacgm_lons[0, 0] - ( aacgmv2.convert_mlt(beam_corners_aacgm_lons[0, 0], date) * 15) beam_corners_mlts_1 = beam_corners_aacgm_lons - mltshift # plot all the beam boundary lines for beam_line in range(beam_range[0], beam_range[1] + 2): plt.plot(beam_corners_mlts_1[gate_range[0]:gate_range[1] + 2, beam_line], beam_corners_aacgm_lats[gate_range[0]:gate_range[1] + 2, beam_line], color='black', linewidth=0.5,