Пример #1
0
    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
Пример #2
0
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],
Пример #5
0
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
Пример #6
0
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
Пример #7
0
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,