Ejemplo n.º 1
0
#
# 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 bz2
import datetime as dt
import matplotlib.pyplot as plt
import pytest
import warnings

import pydarn

with bz2.open('test/data/test.fitacf.bz2') as fp:
    fitacf_stream = fp.read()
data = pydarn.SuperDARNRead(fitacf_stream, True).read_fitacf()


class TestFan_defaults:
    def test_fan_defaults(self):
        """ """
        with warnings.catch_warnings(record=True):
            pydarn.Fan.plot_fan(data)

    def test_fov_series(self):
        """ """
        with warnings.catch_warnings(record=True):
            pydarn.Fan.plot_fov(6, dt.datetime(2020, 4, 4, 6, 2))


@pytest.mark.parametrize('stid', [5, 97])
Ejemplo n.º 2
0
import pydarn
import bz2
import matplotlib.pyplot as plt
from pandas.plotting import register_matplotlib_converters

if __name__ == '__main__':
    fitacf_file = "data/20190318.1001.00.rkn.fitacf.bz2"
    with bz2.open(fitacf_file) as fp:
        fitacf_stream = fp.read()

    sdarn_read = pydarn.SuperDARNRead(fitacf_stream, True)
    fitacf_data = sdarn_read.read_fitacf()
    register_matplotlib_converters()

    pydarn.RTP.plot_summary(fitacf_data, beam_num=2, slant=False)

    plt.show()
Ejemplo n.º 3
0
def convert_fitacf_data(date, in_fname, radar_info):
    day = in_fname.split('.')[0].split('/')[-1]
    month = day[:-2] 
    
    # Keep track of fitACF files that have multiple beam definitions in a
    # monthly log file
    multiBeamLogDir = date.strftime(helper.FIT_NET_LOG_DIR) + month
    multiBeamLogfile = '{dir}/multi_beam_defs_{m}.log'.format(dir = multiBeamLogDir, m = month)

    # Store conversion info like returns outside FOV, missing slist, etc 
    # for each conversion
    conversionLogDir = '{dir}/{d}'.format(dir = multiBeamLogDir, d = day)
    fName = in_fname.split('/')[-1]
    conversionLogfile = '{dir}/{fit}_to_nc.log'.format(dir = conversionLogDir, fit = fName)

    # Define the name of the file holding the list of rawACFs used to 
    # create the fitACF
    rawacfListFilename = '.'.join(in_fname.split('.')[:-1]) + '.rawacfList.txt'

    SDarn_read = pydarn.SuperDARNRead(in_fname)
    data = SDarn_read.read_fitacf()
    bmdata = {
        'rsep': [],
        'frang': [],
    }
    for rec in data:
        for k, v in bmdata.items():
            bmdata[k].append(rec[k])
        if 'slist' in rec.keys():
            if radar_info['maxrg'] < rec['slist'].max():
                radar_info['maxrg'] = rec['slist'].max() + 5
    
    for k, v in bmdata.items():
        val = np.unique(v)
        if len(val) > 1:        
            os.makedirs(conversionLogDir, exist_ok=True)
            os.makedirs(multiBeamLogDir, exist_ok=True)
            
            # Log the multiple beams error in the monthly mutli beam def log
            logText = '{fitacfFullFile} has {numBeamDefs} beam definitions - skipping file conversion.\n'.format(fitacfFullFile = in_fname, numBeamDefs = len(val))
            
            with open(multiBeamLogfile, "a+") as fp: 
                fp.write(logText)

            # Log the multiple beams error in this fitACF's conversion log
            with open(conversionLogfile, "a+") as fp: 
                fp.write(logText)

            return MULTIPLE_BEAM_DEFS_ERROR_CODE, MULTIPLE_BEAM_DEFS_ERROR_CODE
        
        bmdata[k] = int(val)

    # Define FOV
    fov = radFov.fov(
        frang=bmdata['frang'], rsep=bmdata['rsep'], site=None, nbeams=int(radar_info['maxbeams']),
        ngates=int(radar_info['maxrg']), bmsep=radar_info['beamsep'], recrise=radar_info['risetime'], siteLat=radar_info['glat'],
        siteLon=radar_info['glon'], siteBore=radar_info['boresight'], siteAlt=radar_info['alt'], siteYear=date.year,
        elevation=None, altitude=300., hop=None, model='IS',
        coords='geo', date_time=date, coord_alt=0., fov_dir='front',
    )

    # Define fields 
    short_flds = 'tfreq', 'noise.sky', 'cp',
    fov_flds = 'mjd', 'beam', 'range', 'lat', 'lon', 
    data_flds = 'p_l', 'v', 'v_e', 'gflg', 
    elv_flds = 'elv', 'elv_low', 'elv_high',

    # Figure out if we have elevation information
    elv_exists = True
    for rec in data:
        if 'elv' not in rec.keys():
            elv_exists = False
    if elv_exists:
        data_flds += elv_flds

    # Set up data storage
    out = {}
    for fld in (fov_flds + data_flds + short_flds):
        out[fld] = []
   
    # Run through each beam record and store 
    for rec in data:
        time = dt.datetime(rec['time.yr'], rec['time.mo'], rec['time.dy'], rec['time.hr'], rec['time.mt'], rec['time.sc'])
        # slist is the list of range gates with backscatter
        if 'slist' not in rec.keys():
            os.makedirs(conversionLogDir, exist_ok=True)
            logText = 'Could not find slist in record {recordTime} - skipping\n'.format(recordTime = time.strftime('%Y-%m-%d %H:%M:%S'))
            with open(conversionLogfile, "a+") as fp: 
                fp.write(logText)

            continue

        # Can't deal with returns outside of FOV
        if rec['slist'].max() >= fov.slantRCenter.shape[1]:
            os.makedirs(conversionLogDir, exist_ok=True)

            # Log returns outside of FOV
            logText = 'Record {recordTime} found to have a max slist of {maxSList} - skipping record/n'.format(recordTime = time.strftime('%Y-%m-%d %H:%M:%S'), maxSList = rec['slist'].max())
            with open(conversionLogfile, "a+") as fp: 
                fp.write(logText)

            continue

        time = dt.datetime(rec['time.yr'], rec['time.mo'], rec['time.dy'], rec['time.hr'], rec['time.mt'], rec['time.sc'])
        one_obj = np.ones(len(rec['slist'])) 
        mjd = jdutil.jd_to_mjd(jdutil.datetime_to_jd(time))
        bmnum = one_obj * rec['bmnum']
        fovi = fov.beams == rec['bmnum']
        out['mjd'] += (one_obj * mjd).tolist()
        out['beam'] += bmnum.tolist()
        out['range'] += fov.slantRCenter[fovi, rec['slist']].tolist()
        out['lat'] += fov.latCenter[fovi, rec['slist']].tolist()
        out['lon'] += fov.lonCenter[fovi, rec['slist']].tolist()

        for fld in data_flds:
            out[fld] += rec[fld].tolist()
        for fld in short_flds:  # expand out to size
            out[fld] += (one_obj * rec[fld]).tolist()

    # Convert to numpy arrays 
    for k, v in out.items():
        out[k] = np.array(v)

    # Calculate beam azimuths assuming 20 degrees elevation
    beam_off = radar_info['beamsep'] * (fov.beams - (radar_info['maxbeams'] - 1) / 2.0)
    el = 15.
    brng = np.zeros(beam_off.shape)
    for ind, beam_off_elzero in enumerate(beam_off):
        brng[ind] = radFov.calcAzOffBore(el, beam_off_elzero, fov_dir=fov.fov_dir) + radar_info['boresight']

    # Pull the fit version out of the fitACF filename
    fit_version = '.'.join(in_fname.split('.')[-3:-1])

    # Load the list of rawacf files used to create the fitacf and netcdf    
    with open(rawacfListFilename, "rb") as fp:
        rawacf_source_files = pickle.load(fp)

    # Once the list of rawacf source files has been loaded, delete the file used to
    # temporarily store that information
    os.system('rm {rawacfListFile}'.format(rawacfListFile = rawacfListFilename))
    
    hdr = {
        'lat': radar_info['glat'],
        'lon': radar_info['glon'],
        'alt': radar_info['alt'],
        'rsep': bmdata['rsep'],
        'maxrg': radar_info['maxrg'],
        'bmsep': radar_info['beamsep'],
        'boresight': radar_info['boresight'],
        'beams': fov.beams,
        'brng_at_15deg_el': brng,
        'fitacf_version': fit_version,
        'rawacf_source': rawacf_source_files
    }
    return out, hdr
Ejemplo n.º 4
0
# 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 bz2
import datetime as dt
import matplotlib.pyplot as plt
import pytest
import warnings

import pydarn


data = pydarn.SuperDARNRead('test/data/test.grd').read_grid()


class TestGrid_defaults:

    def test_grid_defaults(self):
        """ """
        with warnings.catch_warnings(record=True):
            pydarn.Grid.plot_grid(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('record', [2])
@pytest.mark.parametrize('ranges', [(5,70)])
Ejemplo n.º 5
0
def get_data(in_filename, AtoGHeight):
    print('Processing %s' % in_filename)
    map_data = pydarn.SuperDARNRead(in_filename).read_map()
    latMagVector = []
    lonMagVector = []
    azimuthMagVector = []

    latGeoVector = []
    lonGeoVector = []
    azimuthGeoVector = []

    velocityVector = []
    sdVector = []
    timeVector = []

    for entry in map_data:
        numPoints = len(entry['vector.vel.median'])
        t = dt.datetime(
            int(entry['start.year']),
            int(entry['start.month']),
            int(entry['start.day']),
            int(entry['start.hour']),
            int(entry['start.minute']),
            int(entry['start.second']),
        )
        # Convert time to seconds since 1970-01-01 00:00
        times = [t.timestamp()] * numPoints
        timeVector.append(times)
        azM = entry['vector.kvect']

        velocityVector.append(entry['vector.vel.median'])
        sdVector.append(entry['vector.vel.sd'])
        latMagVector.append(entry['vector.mlat'])
        lonMagVector.append(entry['vector.mlon'])
        azimuthMagVector.append(azM)

        latG, lonG, heightG = aacgmv2.convert_latlon_arr(
            entry['vector.mlat'],
            entry['vector.mlon'],
            AtoGHeight,
            t,
            method_code='A2G',
        )
        latGeoVector.append(latG)
        lonGeoVector.append(lonG)

        azG = convert_azm_aacgm2geo(azM, latG, lonG, t, refAlt=AtoGHeight)
        azimuthGeoVector.append(azG)

    data = {}
    data['times'] = np.array(np.concatenate(timeVector))
    data['mLat'] = np.array(np.concatenate(latMagVector))
    data['mLon'] = np.array(np.concatenate(lonMagVector))
    data['mAz'] = np.array(np.concatenate(azimuthMagVector))
    data['gAz'] = np.array(np.concatenate(azimuthGeoVector))
    data['gLat'] = np.array(np.concatenate(latGeoVector))
    data['gLon'] = np.array(np.concatenate(lonGeoVector))
    data['vel'] = np.array(np.concatenate(velocityVector))
    data['sd'] = np.array(np.concatenate(sdVector))

    return data
def PickleFITACF_occ(station, date, beam_range):
    """
    Take a fitACF file and for each possible echo, record whether or not there was a good echo there
    Note: occurrence rate is not actually computed here, but all the data required to compute it is put into the df

    From Koustov and Syd's paper
    "The echo occurrence rate was computed as a ratio of the number of registered echoes in selected beams and gates
    to the total number of possible echo detections in the same gates over the same period. 15-min averaging
    intervals were considered."

    :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 date: str:
            The date as a string of the form 'yyyymmdd'.  Single digit months and days need to be zero padded.
    :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.

            In the past the following beams have been used:
            - INV 13-15     - DCE 10-12
            - RKN 1-3       - MCM 6-8
            - CLY 4-6       - SPS 3-5
    """

    gate_range = (0, 74)  # TODO: Update this to read in gate range from hardware?

    epoch, date_time = [], []
    slist, beam = [], []
    frang, rsep, tfreq = [], [], []
    good_iono_echo, good_grndscat_echo = [], []

    # Loop through all the files for this station/date
    in_dir = "data/" + station + "/" + station + date
    for in_file in glob.iglob(in_dir + "/*.fitacf.bz2"):
        # Unpack and open the file
        print("     Reading " + in_file + "...")
        try:
            with bz2.open(in_file) as fp:
                fitacf_stream = fp.read()
            sdarn_read = pydarn.SuperDARNRead(fitacf_stream, True)
            fitacf_data = sdarn_read.read_fitacf()
        except BaseException as e:
            # Sometimes files are corrupted, or there is something wrong with them
            print(e)
            pass
        # print("List of available parameters: " + str(fitacf_data[0].keys()))

        # Loop through every record in this file
        for record in range(len(fitacf_data)):
            beam_here = fitacf_data[record]['bmnum']
            if beam_here < beam_range[0] or beam_here > beam_range[1]:
                continue  # We are not interested in records for this beam

            date_time_here, epoch_here = build_datetime_epoch(year=fitacf_data[record]['time.yr'],
                                                              month=fitacf_data[record]['time.mo'],
                                                              day=fitacf_data[record]['time.dy'],
                                                              hour=fitacf_data[record]['time.hr'],
                                                              minute=fitacf_data[record]['time.mt'],
                                                              second=fitacf_data[record]['time.sc'])

            try:
                gates_reporting = np.ndarray.tolist(fitacf_data[record]['slist'])
            except:
                # Sometimes there won't be any gates reporting, this is legacy behaviour and results in partial records
                gates_reporting = []

            for gate in range(gate_range[0], gate_range[1], 1):
                epoch.append(epoch_here)
                date_time.append(date_time_here)
                slist.append(gate)
                beam.append(beam_here)
                tfreq.append(fitacf_data[record]['tfreq'])
                frang.append(fitacf_data[record]['frang'])
                rsep.append(fitacf_data[record]['rsep'])

                try:
                    gate_index = gates_reporting.index(gate)
                    if fitacf_data[record]['qflg'][gate_index] == 1 and fitacf_data[record]['p_l'][gate_index] >= 3:
                        # We have a good echo
                        if fitacf_data[record]['gflg'][gate_index] == 0:
                            # We have a good ionospheric echo
                            good_iono_echo.append(True)
                            good_grndscat_echo.append(False)
                        else:
                            # We have a good ground scatter echo
                            good_grndscat_echo.append(True)
                            good_iono_echo.append(False)

                    else:
                        # Bad echo
                        good_iono_echo.append(False)
                        good_grndscat_echo.append(False)
                except ValueError:
                    # The gate is not in the list of reporting gates
                    good_iono_echo.append(False)
                    good_grndscat_echo.append(False)
                except BaseException as e:
                    print(e)

    if len(epoch) == 0:
        # We have found no data, panic
        raise Exception("PickleFITACF_occ() found no data matching the provided criteria.")

    # Put the data into a dataframe
    print("     Building the data frame...")
    df = pd.DataFrame({'epoch': epoch,      'datetime': date_time,
                       'slist': slist,      'bmnum': beam,
                       'frang': frang,      'rsep': rsep,           'tfreq': tfreq,
                       'good_iono_echo': good_iono_echo,            'good_grndscat_echo': good_grndscat_echo
                       })

    # Save to file
    out_file = in_dir + "/" + station + date + "_occ.pkl"
    print("     Pickling as " + out_file + "...")
    df.to_pickle(out_file)
Ejemplo n.º 7
0
def get_data_occ(station, year_range, month_range, day_range, gate_range,
                 beam_range, freq_range):
    """
    Take a fitACF file and for each possible echo, record whether or not there was a good echo there
    Note: occurrence rate is not actually computed here, but all the data required to compute it is put into the df

    From Koustov and Syd's paper:
    "The echo occurrence rate was computed as a ratio of the number of registered echoes in selected beams and gates
    to the total number of possible echo detections in the same gates over the same period."

    :param station: str:
            The radar station to consider, as 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 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.
    :return: pandas.DataFrame: A dataframe with select fitACF parameters.
    """

    # loc_root = "/data/fitacf_30"  # fitACF 3.0
    loc_root = "/data/fitacf_25"  # fitACF 2.5

    # Create empty arrays for the parameters we need
    epoch, date_time = [], []
    slist, beam = [], []
    frang, rsep, tfreq = [], [], []
    good_iono_echo, good_grndscat_echo = [], []

    # Data on maxwell.usask.ca is stored in a file structure that looks like this:
    # /data/fitacf_30/<year>/<month>/<year><month><day>.<start-time>.<radar-site>.fitacf.bz2
    for in_dir_parent in glob.iglob(loc_root + "/*"):
        year_here = int(os.path.basename(in_dir_parent))

        if year_range[0] <= year_here <= year_range[1]:
            for in_dir in glob.iglob(in_dir_parent + "/*"):
                month_here = int(os.path.basename(in_dir))

                if month_range[0] <= month_here <= month_range[1]:
                    for in_file in glob.iglob(in_dir + "/*" + station + "*"):
                        day_here = int(os.path.basename(in_file)[6:8])

                        if day_range[0] <= day_here <= day_range[1]:
                            # We will read in the whole day, and worry about hour restrictions later
                            print("    Reading: " + str(in_file))
                            try:
                                with bz2.open(in_file) as fp:
                                    fitacf_stream = fp.read()
                                sdarn_read = pydarn.SuperDARNRead(
                                    fitacf_stream, True)
                                fitacf_data = sdarn_read.read_fitacf(
                                )  # this is
                            except BaseException:
                                # Sometimes files are corrupted, or there is something wrong with them
                                pass

                            # Loop through every record in this file
                            for record in range(len(fitacf_data)):
                                beam_here = fitacf_data[record]['bmnum']
                                if beam_here < beam_range[
                                        0] or beam_here > beam_range[1]:
                                    continue  # We are not interested in records for this beam

                                date_time_here, epoch_here = build_datetime_epoch_local(
                                    year=fitacf_data[record]['time.yr'],
                                    month=fitacf_data[record]['time.mo'],
                                    day=fitacf_data[record]['time.dy'],
                                    hour=fitacf_data[record]['time.hr'],
                                    minute=fitacf_data[record]['time.mt'],
                                    second=fitacf_data[record]['time.sc'])

                                try:
                                    gates_reporting = np.ndarray.tolist(
                                        fitacf_data[record]['slist'])
                                except:
                                    # Sometimes there won't be any gates reporting, this is legacy behaviour and
                                    #  results in partial records
                                    gates_reporting = []

                                for gate in range(gate_range[0], gate_range[1],
                                                  1):
                                    epoch.append(epoch_here)
                                    date_time.append(date_time_here)
                                    slist.append(gate)
                                    beam.append(beam_here)
                                    tfreq.append(fitacf_data[record]['tfreq'])
                                    frang.append(fitacf_data[record]['frang'])
                                    rsep.append(fitacf_data[record]['rsep'])

                                    try:
                                        gate_index = gates_reporting.index(
                                            gate)
                                        if fitacf_data[record]['qflg'][gate_index] == 1 and \
                                                fitacf_data[record]['p_l'][gate_index] >= 3:
                                            # We have a good echo
                                            if fitacf_data[record]['gflg'][
                                                    gate_index] == 0:
                                                # We have a good ionospheric echo
                                                good_iono_echo.append(True)
                                                good_grndscat_echo.append(
                                                    False)
                                            else:
                                                # We have a good ground scatter echo
                                                good_grndscat_echo.append(True)
                                                good_iono_echo.append(False)

                                        else:
                                            # Bad echo
                                            good_iono_echo.append(False)
                                            good_grndscat_echo.append(False)
                                    except ValueError:
                                        # The gate is not in the list of reporting gates
                                        good_iono_echo.append(False)
                                        good_grndscat_echo.append(False)
                                    except BaseException as e:
                                        print(e)

    if len(epoch) == 0:
        # We have found no data, panic
        warnings.warn(
            "get_data_occ() found no data matching the provided criteria.  "
            "year_range: " + str(year_range) + ", month_range:" +
            str(month_range) + ", day_range" + str(day_range),
            category=Warning)

    # Put the data into a dataframe
    print("     Building the data frame...")
    df = pd.DataFrame({
        'epoch': epoch,
        'datetime': date_time,
        'slist': slist,
        'bmnum': beam,
        'frang': frang,
        'rsep': rsep,
        'tfreq': tfreq,
        'good_iono_echo': good_iono_echo,
        'good_grndscat_echo': good_grndscat_echo
    })

    # Filter the data for the needed beam, gate, and freq ranges
    df = df.loc[(df['bmnum'] >= beam_range[0]) & (df['bmnum'] <= beam_range[1])
                & (df['slist'] >= gate_range[0]) &
                (df['slist'] <= gate_range[1]) &

                # Note: freq_range is in MHz while data in 'tfreq' is in kHz
                (df['tfreq'] >= freq_range[0] * 1000) &
                (df['tfreq'] <= freq_range[1] * 1000)]

    return df
Ejemplo n.º 8
0
def get_data(station, year_range, month_range, day_range, hour_range, gate_range, beam_range, freq_range):
    """
    Get all of the SuperDARN data within the desired range/time, put it into a dataframe,
    and then return the dataframe for plotting/analysis

    Dataframe keys follow the same convention as fitACF:
        https://radar-software-toolkit-rst.readthedocs.io/en/latest/references/general/fitacf/

    :param station: str:
            The radar station to consider, as 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 freq_range: (<float>, <float>) (optional):
            Inclusive.  The frequency range to consider in MHz.
            If omitted (or None), then all frequencies are considered.
    :return: pandas.DataFrame: A dataframe with select fitACF parameters.
    """

    loc_root = "/data/fitacf_30"    # fitACF 3.0
    # loc_root = "/data/fitacf_25"  # fitACF 2.5
    pattern = "%Y.%m.%d %H:%M:%S"

    # Create empty arrays for scalar parameters
    epoch, date_time = [], []
    bmnum = []
    tfreq = []
    frang, rsep = [], []

    # Create empty arrays for vector parameters
    slist = []
    qflg, gflg = [], []
    v, p_l, w_l = [], [], []
    phi0, elv = [], []

    # Data on maxwell.usask.ca is stored in a file structure that looks like this:
    # /data/fitacf_30/<year>/<month>/<year><month><day>.<start-time>.<radar-site>.fitacf.bz2
    for in_dir_parent in glob.iglob(loc_root + "/*"):
        year_here = int(os.path.basename(in_dir_parent))

        if year_range[0] <= year_here <= year_range[1]:
            for in_dir in glob.iglob(in_dir_parent + "/*"):
                month_here = int(os.path.basename(in_dir))

                if month_range[0] <= month_here <= month_range[1]:
                    for in_file in glob.iglob(in_dir + "/*" + station + "*"):
                        day_here = int(os.path.basename(in_file)[6:8])

                        if day_range[0] <= day_here <= day_range[1]:
                            # We will read in the whole day, and worry about hour restrictions later
                            print("    Reading: " + str(in_file))
                            try:
                                with bz2.open(in_file) as fp:
                                    fitacf_stream = fp.read()
                                sdarn_read = pydarn.SuperDARNRead(fitacf_stream, True)
                                fitacf_data = sdarn_read.read_fitacf()  # this is
                            except BaseException:
                                # Sometimes files are corrupted, or there is something wrong with them
                                pass

                            # Loop through all the scans and build up the arrays
                            # As far as I know, list extensions are the fastest way to do this
                            for scan in range(len(fitacf_data)):

                                date_time_here, epoch_here = build_datetime_epoch_local(
                                    year=fitacf_data[scan]['time.yr'],
                                    month=fitacf_data[scan]['time.mo'],
                                    day=fitacf_data[scan]['time.dy'],
                                    hour=fitacf_data[scan]['time.hr'],
                                    minute=fitacf_data[scan]['time.mt'],
                                    second=fitacf_data[scan]['time.sc'])

                                try:
                                    num_gates_reporting = len(fitacf_data[scan]['slist'])
                                except:
                                    # Sometimes there won't be any gates reporting, this is legacy behaviour and
                                    #  results in partial records
                                    num_gates_reporting = 0

                                if num_gates_reporting > 0:
                                    # Build up scalar parameters
                                    epoch.extend([epoch_here] * num_gates_reporting)
                                    date_time.extend([date_time_here] * num_gates_reporting)
                                    bmnum.extend([fitacf_data[scan]['bmnum']] * num_gates_reporting)
                                    tfreq.extend([fitacf_data[scan]['tfreq']] * num_gates_reporting)
                                    frang.extend([fitacf_data[scan]['frang']] * num_gates_reporting)
                                    rsep.extend([fitacf_data[scan]['rsep']] * num_gates_reporting)

                                    # Build up vector parameters
                                    slist.extend(fitacf_data[scan]['slist'])
                                    qflg.extend(fitacf_data[scan]['qflg'])
                                    gflg.extend(fitacf_data[scan]['gflg'])
                                    v.extend(fitacf_data[scan]['v'])
                                    p_l.extend(fitacf_data[scan]['p_l'])
                                    w_l.extend(fitacf_data[scan]['w_l'])
                                    try:
                                        phi0.extend(fitacf_data[scan]['phi0'])
                                    except KeyError:
                                        # Some files might not have this parameter
                                        phi0.extend([math.nan] * num_gates_reporting)
                                    except BaseException:
                                        raise
                                    try:
                                        elv.extend(fitacf_data[scan]['elv'])
                                    except KeyError:
                                        # Some files might not have this parameter
                                        elv.extend([math.nan] * num_gates_reporting)
                                    except BaseException:
                                        raise

    if len(epoch) == 0:
        # We have found no data, panic
        raise Exception("get_data() found no data matching the provided criteria.")

    df = pd.DataFrame({'epoch': epoch,      'datetime': date_time,
                       'bmnum': bmnum,
                       'tfreq': tfreq,
                       'frang': frang,      'rsep': rsep,

                       'slist': slist,
                       'qflg': qflg,        'gflg': gflg,
                       'v': v,              'p_l': p_l,                 'w_l': w_l,
                       'phi0': phi0,        'elv': elv
                       })

    # Filter the data for the needed time, beam, gate, and freq ranges
    df = df.loc[(df['datetime'].hour >= hour_range[0]) & (df['datetime'].hour < hour_range[1]) &
                (df['bmnum'] >= beam_range[0]) & (df['bmnum'] <= beam_range[1]) &
                (df['slist'] >= gate_range[0]) & (df['slist'] <= gate_range[1]) &

                # Note: freq_range is in MHz while data in 'tfreq' is in kHz
                (df['tfreq'] >= freq_range[0] * 1000) & (df['tfreq'] <= freq_range[1] * 1000)]

    # Until I have an application that requires bad quality points, I will assume they always need to be filtered out
    df = df.loc[(df['qflg'] == 1)]

    df.drop(columns=['qflg'], inplace=True)
    df.reset_index(drop=True, inplace=True)

    return df