Example #1
0
def calcFieldPnt(tr_glat,
                 tr_glon,
                 tr_alt,
                 boresight,
                 beam_off,
                 slant_range,
                 adjusted_sr=True,
                 elevation=None,
                 altitude=None,
                 hop=None,
                 model=None,
                 coords='geo',
                 gs_loc="G",
                 max_vh=400.0,
                 fov_dir='front'):
    """Calculate coordinates of field point given the radar coordinates and
    boresight, the pointing direction deviation from boresight and elevation
    angle, and the field point slant range and altitude. Either the elevation
    or the altitude must be provided. If none is provided, the altitude is set
    to 300 km and the elevation evaluated to accomodate altitude and range.

    Parameters
    ----------
    tr_glat
        transmitter latitude [degree, N]
    tr_glon
        transmitter longitude [degree, E]
    tr_alt
        transmitter altitude [km]
    boresight
        boresight azimuth [degree, E]
    beam_off
        beam azimuthal offset from boresight [degree]
    slant_range
        slant range [km]
    adjusted_sr : Optional(bool)
        Denotes whether or not the slant range is the total measured slant
        range (False) or if it has been adjusted to be the slant distance to
        last ionospheric reflection point (True).  (default=True)
    elevation : Optional[float]
        elevation angle [degree] (estimated if None)
    altitude : Optional[float]
        altitude [km] (default 300 km)
    hop : Optional[float]
        backscatter hop (ie 0.5, 1.5 for ionospheric; 1.0, 2.0 for ground)
    model : Optional[str]
        IS : for standard ionopsheric scatter projection model (ignores hop)
        GS : for standard ground scatter projection model (ignores hop)
        S : for standard projection model (uses hop)
        E1 : for Chisham E-region 1/2-hop ionospheric projection model
        F1 : for Chisham F-region 1/2-hop ionospheric projection model
        F3 : for Chisham F-region 1-1/2-hop ionospheric projection model
        C : for Chisham projection model (ionospheric only, ignores hop,
            requires total measured slant range)
        None : if you trust your elevation or altitude values. more to come
    coords
        'geo' (more to come)
    gs_loc : (str)
        Provide last ground scatter location 'G' or ionospheric refraction
        location 'I' for groundscatter (default='G')
    max_vh : (float)
        Maximum height for longer slant ranges in Standard model (default=400)
    fov_dir
        'front' (default) or 'back'.  Specifies fov direction

    Returns
    ---------
    geo_dict['geoLat'] : (float or np.ndarray)
        Field point latitude(s) in degrees or np.nan if error
    geo_dict['geoLon'] : (float or np.ndarray)
        Field point longitude(s) in degrees or np.nan if error
    """
    from davitpy.utils import Re, geoPack
    import davitpy.utils.model_vheight as vhm

    # Only geo is implemented.
    if coords != "geo":
        logging.error("Only geographic (geo) is implemented in calcFieldPnt.")
        return np.nan, np.nan

    # Use model to get altitude if desired
    xalt = np.nan
    calt = None
    if model is not None:
        if model in ['IS', 'GS', 'S']:
            # The standard model can be used with or without an input altitude
            # or elevation.  Returns an altitude that has been adjusted to
            # comply with common scatter distributions
            if hop is None:
                if model == "S":
                    # Default to ionospheric backscatter if hop not specified
                    hop = 0.5
                else:
                    hop = 0.5 if model == "IS" else 1.0

            xalt = vhm.standard_vhm(slant_range,
                                    adjusted_sr=adjusted_sr,
                                    max_vh=max_vh,
                                    hop=hop,
                                    alt=altitude,
                                    elv=elevation)
        else:
            # The Chisham model uses only the total slant range to determine
            # altitude based on years of backscatter data at SAS.  Performs
            # significantly better than the standard model for ionospheric
            # backscatter, not tested on groundscatter
            if adjusted_sr:
                logging.error("Chisham model needs total slant range")
                return np.nan, np.nan

            # Use Chisham model to calculate virtual height
            cmodel = None if model == "C" else model
            xalt, shop = vhm.chisham_vhm(slant_range, cmodel, hop_output=True)

            # If hop is not known, set using model divisions
            if hop is None:
                hop = shop

            # If hop is greater than 1/2, the elevation angle needs to be
            # calculated from the ground range rather than the virtual height
            if hop > 0.5:
                calt = float(xalt)

    elif elevation is None or np.isnan(elevation):
        if hop is None or adjusted_sr:
            logging.error("Total slant range and hop needed with measurements")
            return np.nan, np.nan
        if altitude is None or np.isnan(altitude):
            logging.error("No observations supplied")
            return np.nan, np.nan

        # Adjust slant range if there is groundscatter and the location
        # desired is the ionospheric reflection point
        asr = slant_range
        if hop == np.floor(hop) and gs_loc == "I":
            asr *= 1.0 - 1.0 / (2.0 * hop)

        # Adjust altitude if it's unrealistic
        if asr < altitude:
            altitude = asr - 10

        xalt = altitude

    # Use model altitude to determine elevation angle and then the location,
    # or if elevation angle was supplied, find the location
    if not np.isnan(xalt):
        # Since we have a modeled or measured altitude, start by setting the
        # Earth radius below field point to Earth radius at radar
        (lat, lon, tr_rad) = geoPack.geodToGeoc(tr_glat, tr_glon)
        rad_pos = tr_rad

        # Iterate until the altitude corresponding to the calculated elevation
        # matches the desired altitude.  Assumes straight-line path to last
        # ionospheric scattering point, so adjust slant range if necessary
        # for groundscatter
        asr = slant_range
        shop = hop
        if not adjusted_sr and hop == np.floor(hop) and gs_loc == "I":
            asr *= 1.0 - 1.0 / (2.0 * hop)
            shop = hop - 0.5

        # Set safty counter and iteratively determine location
        maxn = 30
        hdel = 100.0
        htol = 0.5
        if (slant_range >= 800.0 and model != 'GS') or shop > 1.0:
            htol = 5.0
        n = 0
        while n < maxn:
            tr_dist = tr_rad + tr_alt
            if calt is not None:
                # Adjust elevation angle for any hop > 1 (Chisham et al. 2008)
                pos_dist = rad_pos + calt
                phi = np.arccos((tr_dist**2 + pos_dist**2 - asr**2) /
                                (2.0 * tr_dist * pos_dist))
                beta = np.arcsin((tr_dist * np.sin(phi / (shop * 2.0))) /
                                 (asr / (shop * 2.0)))
                tel = np.pi / 2.0 - beta - phi / (shop * 2.0)

                if xalt == calt:
                    xalt = np.sqrt(tr_rad**2 + asr**2 +
                                   2.0 * asr * tr_rad * np.sin(tel)) - tr_rad
                tel = np.degrees(tel)
            else:
                # pointing elevation (spherical Earth value) [degree]
                tel = np.arcsin(((rad_pos + xalt)**2 - tr_dist**2 - asr**2) /
                                (2.0 * tr_dist * asr))
                tel = np.degrees(tel)

            # estimate off-array-normal azimuth (because it varies slightly
            # with elevation) [degree]
            boff = calcAzOffBore(tel, beam_off, fov_dir=fov_dir)
            # pointing azimuth
            taz = boresight + boff
            # calculate position of field point
            geo_dict = geoPack.calcDistPnt(tr_glat,
                                           tr_glon,
                                           tr_alt,
                                           dist=asr,
                                           el=tel,
                                           az=taz)

            # Update Earth radius
            rad_pos = geo_dict['distRe']

            # stop if the altitude is what we want it to be (or close enough)
            new_hdel = abs(xalt - geo_dict['distAlt'])
            if new_hdel <= htol:
                break

            # stop unsuccessfully if the altitude difference hasn't improved
            if abs(new_hdel - hdel) < 1.0e-3:
                n = maxn

            # Prepare the next iteration
            hdel = new_hdel
            n += 1

        if n >= maxn:
            estr = 'Accuracy on height calculation ({}) not '.format(htol)
            estr = '{:s}reached quick enough. Returning nan, nan.'.format(estr)
            logging.warning(estr)
            return np.nan, np.nan
        else:
            return geo_dict['distLat'], geo_dict['distLon']
    elif elevation is not None:
        # No projection model (i.e., the elevation or altitude is so good that
        # it gives you the proper projection by simple geometric
        # considerations). Using no models simply means tracing based on
        # trustworthy elevation or altitude
        if hop is None or adjusted_sr:
            logging.error("Hop and total slant range needed with measurements")
            return np.nan, np.nan

        if np.isnan(elevation):
            logging.error("No observations provided")
            return np.nan, np.nan

        shop = hop - 0.5 if hop == np.floor(hop) and gs_loc == "I" else hop
        asr = slant_range
        if hop > 0.5 and hop != shop:
            asr *= 1.0 - 1.0 / (2.0 * hop)

        # The tracing is done by calcDistPnt
        boff = calcAzOffBore(elevation, beam_off, fov_dir=fov_dir)
        geo_dict = geoPack.calcDistPnt(tr_glat,
                                       tr_glon,
                                       tr_alt,
                                       dist=asr,
                                       el=elevation,
                                       az=boresight + boff)

        return geo_dict['distLat'], geo_dict['distLon']
Example #2
0
def calcFieldPnt(tr_glat, tr_glon, tr_alt, boresight, beam_off, slant_range,
                 adjusted_sr=True, elevation=None, altitude=None, hop=None,
                 model=None, coords='geo', gs_loc="G", max_vh=400.0,
                 fov_dir='front', eval_loc=False):
    """Calculate coordinates of field point given the radar coordinates and
    boresight, the pointing direction deviation from boresight and elevation
    angle, and the field point slant range and altitude. Either the elevation
    or the altitude must be provided. If none is provided, the altitude is set
    to 300 km and the elevation evaluated to accomodate altitude and range.

    Parameters
    ----------
    tr_glat
        transmitter latitude [degree, N]
    tr_glon
        transmitter longitude [degree, E]
    tr_alt
        transmitter altitude [km]
    boresight
        boresight azimuth [degree, E]
    beam_off
        beam azimuthal offset from boresight [degree]
    slant_range
        slant range [km]
    adjusted_sr : Optional(bool)
        Denotes whether or not the slant range is the total measured slant
        range (False) or if it has been adjusted to be the slant distance to
        last ionospheric reflection point (True).  (default=True)
    elevation : Optional[float]
        elevation angle [degree] (estimated if None)
    altitude : Optional[float]
        altitude [km] (default 300 km)
    hop : Optional[float]
        backscatter hop (ie 0.5, 1.5 for ionospheric; 1.0, 2.0 for ground)
    model : Optional[str]
        IS : for standard ionopsheric scatter projection model (ignores hop)
        GS : for standard ground scatter projection model (ignores hop)
        S : for standard projection model (uses hop)
        E1 : for Chisham E-region 1/2-hop ionospheric projection model
        F1 : for Chisham F-region 1/2-hop ionospheric projection model
        F3 : for Chisham F-region 1-1/2-hop ionospheric projection model
        C : for Chisham projection model (ionospheric only, ignores hop,
            requires total measured slant range)
        None : if you trust your elevation or altitude values. more to come
    coords
        'geo' (more to come)
    gs_loc : (str)
        Provide last ground scatter location 'G' or ionospheric refraction
        location 'I' for groundscatter (default='G')
    max_vh : (float)
        Maximum height for longer slant ranges in Standard model (default=400)
    fov_dir : (str)
        'front' (default) or 'back'.  Specifies fov direction
    eval_loc : (bool)
        Evaluate the calcualted location based on reasonable tolerances (True)
        or accept the first calculation (False).  Using True gives better
        locations, but restricts data at the furthest range gates.
        (default=False)

    Returns
    ---------
    geo_dict['geoLat'] : (float or np.ndarray)
        Field point latitude(s) in degrees or np.nan if error
    geo_dict['geoLon'] : (float or np.ndarray)
        Field point longitude(s) in degrees or np.nan if error
    """
    from davitpy.utils import Re, geoPack
    import davitpy.utils.model_vheight as vhm

    # Only geo is implemented.
    if coords != "geo":
        logging.error("Only geographic (geo) is implemented in calcFieldPnt.")
        return np.nan, np.nan

    # Use model to get altitude if desired
    xalt = np.nan
    calt = None
    if model is not None:
        if model in ['IS', 'GS', 'S']:
            # The standard model can be used with or without an input altitude
            # or elevation.  Returns an altitude that has been adjusted to
            # comply with common scatter distributions
            if hop is None:
                if model == "S":
                    # Default to ionospheric backscatter if hop not specified
                    hop = 0.5
                else:
                    hop = 0.5 if model == "IS" else 1.0

            xalt = vhm.standard_vhm(slant_range, adjusted_sr=adjusted_sr,
                                    max_vh=max_vh, hop=hop, alt=altitude,
                                    elv=elevation)
        else:
            # The Chisham model uses only the total slant range to determine
            # altitude based on years of backscatter data at SAS.  Performs
            # significantly better than the standard model for ionospheric
            # backscatter, not tested on groundscatter
            if adjusted_sr:
                logging.error("Chisham model needs total slant range")
                return np.nan, np.nan

            # Use Chisham model to calculate virtual height
            cmodel = None if model == "C" else model
            xalt, shop = vhm.chisham_vhm(slant_range, cmodel, hop_output=True)

            # If hop is not known, set using model divisions
            if hop is None:
                hop = shop

            # If hop is greater than 1/2, the elevation angle needs to be
            # calculated from the ground range rather than the virtual height
            if hop > 0.5:
                calt = float(xalt)

    elif elevation is None or np.isnan(elevation):
        if hop is None or adjusted_sr:
            logging.error("Total slant range and hop needed with measurements")
            return np.nan, np.nan
        if altitude is None or np.isnan(altitude):
            logging.error("No observations supplied")
            return np.nan, np.nan

        # Adjust slant range if there is groundscatter and the location
        # desired is the ionospheric reflection point
        asr = slant_range
        if hop == np.floor(hop) and gs_loc == "I":
            asr *= 1.0 - 1.0 / (2.0 * hop)

        # Adjust altitude if it's unrealistic
        if asr < altitude:
            altitude = asr - 10

        xalt = altitude

    # Use model altitude to determine elevation angle and then the location,
    # or if elevation angle was supplied, find the location
    if not np.isnan(xalt):
        # Since we have a modeled or measured altitude, start by setting the
        # Earth radius below field point to Earth radius at radar
        (lat, lon, tr_rad) = geoPack.geodToGeoc(tr_glat, tr_glon)
        rad_pos = tr_rad

        # Iterate until the altitude corresponding to the calculated elevation
        # matches the desired altitude.  Assumes straight-line path to last
        # ionospheric scattering point, so adjust slant range if necessary
        # for groundscatter
        asr = slant_range
        shop = hop
        if not adjusted_sr and hop == np.floor(hop) and gs_loc == "I":
            asr *= 1.0 - 1.0 / (2.0 * hop)
            shop = hop - 0.5

        # Set safty counter and iteratively determine location
        maxn = 30
        hdel = 100.0
        htol = 0.5
        if (slant_range >= 800.0 and model != 'GS') or shop > 1.0:
            htol = 5.0
        n = 0
        while n < maxn:
            tr_dist = tr_rad + tr_alt
            if calt is not None:
                # Adjust elevation angle for any hop > 1 (Chisham et al. 2008)
                pos_dist = rad_pos + calt
                phi = np.arccos((tr_dist**2 + pos_dist**2 - asr**2) /
                                (2.0 * tr_dist * pos_dist))
                beta = np.arcsin((tr_dist * np.sin(phi / (shop * 2.0))) /
                                 (asr / (shop * 2.0)))
                tel = np.pi / 2.0 - beta - phi / (shop * 2.0)

                if xalt == calt:
                    xalt = np.sqrt(tr_rad**2 + asr**2 + 2.0 * asr * tr_rad *
                                   np.sin(tel)) - tr_rad
                tel = np.degrees(tel)
            else:
                # pointing elevation (spherical Earth value) [degree]
                tel = np.arcsin(((rad_pos + xalt)**2 - tr_dist**2 - asr**2) /
                                (2.0 * tr_dist * asr))
                tel = np.degrees(tel)

            # estimate off-array-normal azimuth (because it varies slightly
            # with elevation) [degree]
            boff = calcAzOffBore(tel, beam_off, fov_dir=fov_dir)
            # pointing azimuth
            taz = boresight + boff
            # calculate position of field point
            geo_dict = geoPack.calcDistPnt(tr_glat, tr_glon, tr_alt,
                                           dist=asr, el=tel, az=taz)

            # Update Earth radius
            rad_pos = geo_dict['distRe']

            # stop if the altitude is what we want it to be (or close enough)
            new_hdel = abs(xalt - geo_dict['distAlt'])
            if new_hdel <= htol or not eval_loc:
                break

            # stop unsuccessfully if the altitude difference hasn't improved
            if abs(new_hdel - hdel) < 1.0e-3:
                n = maxn

            # Prepare the next iteration
            hdel = new_hdel
            n += 1

        if n >= maxn:
            estr = 'Accuracy on height calculation ({}) not '.format(htol)
            estr = '{:s}reached quick enough. Returning nan, nan.'.format(estr)
            logging.warning(estr)
            return np.nan, np.nan
        else:
            return geo_dict['distLat'], geo_dict['distLon']
    elif elevation is not None:
        # No projection model (i.e., the elevation or altitude is so good that
        # it gives you the proper projection by simple geometric
        # considerations). Using no models simply means tracing based on
        # trustworthy elevation or altitude
        if hop is None or adjusted_sr:
            logging.error("Hop and total slant range needed with measurements")
            return np.nan, np.nan

        if np.isnan(elevation):
            logging.error("No observations provided")
            return np.nan, np.nan

        shop = hop - 0.5 if hop == np.floor(hop) and gs_loc == "I" else hop
        asr = slant_range
        if hop > 0.5 and hop != shop:
            asr *= 1.0 - 1.0 / (2.0 * hop)

        # The tracing is done by calcDistPnt
        boff = calcAzOffBore(elevation, beam_off, fov_dir=fov_dir)
        geo_dict = geoPack.calcDistPnt(tr_glat, tr_glon, tr_alt, dist=asr,
                                       el=elevation, az=boresight + boff)

        return geo_dict['distLat'], geo_dict['distLon']
Example #3
0
def calc_virtual_height(beam,
                        radius,
                        elv=list(),
                        elv_attr="elv",
                        dist=list(),
                        dist_attr="slist",
                        dist_units=None,
                        dist_adjust=False,
                        hop=list(),
                        hop_attr="hop",
                        model=None,
                        max_vh=400.0):
    """Calculate the virtual height for a specified slant range using
    elevation or a model

    Parameters
    -----------
    beam : (class `pydarn.sdio.radDataTypes.beamData`)
        Data for a single radar and beam along all range gates at a given time
    radius : (float)
        Earth radius in km
    elv : (list or numpy.ndarray)
        List containing elevation in degrees, or nothing to load the
        elevation from the beam (default=list())
    elv_attr : (string)
        Name of beam attribute containing the elevation (default="elv")
    dist : (list or numpy.ndarray)
        List containing the slant distance from the radar to the reflection
        location of the backscatter, or nothing to load the distance from the
        beam. (default=list())
    dist_attr : (str)
        Name of beam attribute containing the slant distance.  (default="slist")
    dist_units : (string or NoneType)
        Units of the slant distance to backscatter location data.  May supply
        "km", "m", or None.  None indicates that the distance is in range gate
        bins. (default=None)
    dist_adjust : (bool)
        Denotes whether or not the slant distance has been adjusted for hop.
        (default=False)
    hop : (list or numpy.ndarray)
        List containing the hop for each point.  If empty, will assume 0.5-hop
        (default=list())
    hop_attr : (string)
        Name of beam attribute containing the hop (default="hop")
    model : (str or NoneType)
        Calculate virtual height using elevation (None) or model? (default=None)
        Available models
        ----------------
        IS : for standard ionopsheric scatter projection model (ignores hop)
        GS : for standard ground scatter projection model (ignores hop)
        S : for standard projection model (uses hop)
        E1 : for Chisham E-region 1/2-hop ionospheric projection model
        F1 : for Chisham F-region 1/2-hop ionospheric projection model
        F3 : for Chisham F-region 1-1/2-hop ionospheric projection model
        C : for Chisham projection model (ionospheric only, ignores hop,
            requires only total measured slant range)
    max_vh : (float)
        Maximum height for longer slant ranges in Standard model (default=400)
    
    Returns
    --------
    height : (numpy.array)
        An array of floats of the same size as the myBeam.fit.slist list,
        containing the virtual height for each range gate or NaN if a virtual
        height could not be calculated

    Notes
    --------
    Specifying a single earth radius introduces additional error into the
    resulting heights.  If the terrestrial radius at the radar location is used,
    this error is on the order of 0.01-0.1 km (much smaller than the difference
    between the real and virtual height).

    The available models are only 
    """
    import davitpy.pydarn.sdio as sdio
    import davitpy.utils.model_vheight as vhm
    s_model = ["S", "IS", "GS"]
    c_model = ["C", "F1", "F3", "E1"]

    #---------------------------------
    # Check the input
    if not isinstance(beam, sdio.radDataTypes.beamData):
        logging.error('the beam must be a beamData class')
        return None

    if not isinstance(radius, float):
        logging.error('the radius must be a float')
        return None

    if not (model is None or model in s_model or model in c_model):
        logging.error('unknown model [{:}]'.format(model))
        return None

    #---------------------------------
    # Load the slant range/distance
    if len(dist) == 0 or (len(elv) > 0 and len(elv) != len(dist)):
        try:
            dist = getattr(beam.fit, dist_attr)

            if dist is None:
                logging.error('no range/distance data available')
                return None
        except:
            logging.error('no range attribute [{:s}]'.format(dist_attr))
            return None

    if len(dist) == 0:
        logging.error("unable to calculate h' without slant range")
        return None

    #---------------------------------
    # Load elevation
    if not model in c_model:
        estr = None
        if len(elv) == 0 or len(elv) != len(dist):
            try:
                elv = getattr(beam.fit, elv_attr)

                if model is None:
                    if elv is None:
                        logging.error('no elevation available')
                        return None
                    elif len(dist) != len(elv):
                        logging.error('unequal distance and elevation arrays')
                        return None
                else:
                    if elv is None:
                        estr = 'no elevation available'
                    elif len(dist) != len(elv):
                        estr = 'unequal distance and elevation arrays'
            except:
                estr = 'no elevation attribute [{:s}]'.format(elv_attr)
                if model is None:
                    logging.error(estr)
                    return None

        if estr is not None:
            logging.warn(estr)
            elv = [None for d in dist]

    if model is None and (len(elv) == 0 or len(elv) != len(dist)):
        logging.error("unable to load matching elevation and distance lists")
        return None

    #---------------------------------
    # Load hop
    if len(hop) == 0 or (len(dist) > 0 and len(hop) != len(dist)):
        estr = None
        try:
            hop = getattr(beam.fit, hop_attr)

            if hop is None:
                estr = 'no hop available'
        except:
            estr = 'no hop attribute [{:s}]'.format(hop_attr)

        if estr is not None:
            logging.warn(estr)

            if model in c_model:
                hop = [None for d in dist]
            else:
                hop = [0.5 for d in dist]

    #---------------------------------------------------------------------
    # If the hop attribute was actually the groundscatter flag, adjust
    if hop_attr == "gflg":
        hop = [1.0 if gg == 1 else 0.5 for gg in hop]

    #---------------------------------------------------------------------
    # Ensure that the range/distance (and error) are in km
    if dist_units is None:
        # Convert from range gates to km
        dist = list(5.0e-10 * scicon.c *
                    (np.array(dist) * beam.prm.smsep + beam.prm.lagfr))
    elif dist_units is "m":
        # Convert from meters to km
        dist = [d / 1000.0 for d in dist]
    elif dist_units is not "km":
        logging.error('unknown range unit [{:s}]'.format(dist_units))
        return None

    #-----------------------------------------------------------------------
    # Cycle through the beams and elevations, calculating the virtual height
    height = np.empty(shape=(len(dist), ), dtype=float) * np.nan

    for i, d in enumerate(dist):
        if model in s_model:
            # Calculate height using standard model
            hh = hop[i]
            if model != "S":
                hh = 0.5 if model == "IS" else 1.0

            height[i] = vhm.standard_vhm(d,
                                         adjusted_sr=dist_adjust,
                                         max_vh=max_vh,
                                         hop=hh,
                                         elv=elv[i])
        elif model in c_model:
            # Calculate height using Chisham model
            cm = None if model == "C" else model
            height[i] = vhm.chisham_vhm(d, vhmtype=cm, hop_output=False)
        elif model is None and not np.isnan(elv[i]):
            # Calculate height by assuming a spherical earth and solving the
            # law of cosines for an obtuse triangle with sides radius,
            # radius + height, and distance, where the angle between the side of
            # length distance and radius + height is equal to 90 deg - elevation
            if not dist_adjust:
                d /= hop[i] * 2.0

            hsqrt = np.sqrt(d**2 + radius**2 +
                            2.0 * d * radius * np.sin(np.radians(elv[i])))
            height[i] = hsqrt - radius

    return height
Example #4
0
def calc_virtual_height(beam, radius, elv=list(), elv_attr="elv", dist=list(),
                        dist_attr="slist", dist_units=None, dist_adjust=False,
                        hop=list(), hop_attr="hop", model=None, max_vh=400.0):
    """Calculate the virtual height for a specified slant range using
    elevation or a model

    Parameters
    -----------
    beam : (class `pydarn.sdio.radDataTypes.beamData`)
        Data for a single radar and beam along all range gates at a given time
    radius : (float)
        Earth radius in km
    elv : (list or numpy.ndarray)
        List containing elevation in degrees, or nothing to load the
        elevation from the beam (default=list())
    elv_attr : (string)
        Name of beam attribute containing the elevation (default="elv")
    dist : (list or numpy.ndarray)
        List containing the slant distance from the radar to the reflection
        location of the backscatter, or nothing to load the distance from the
        beam. (default=list())
    dist_attr : (str)
        Name of beam attribute containing the slant distance.  (default="slist")
    dist_units : (string or NoneType)
        Units of the slant distance to backscatter location data.  May supply
        "km", "m", or None.  None indicates that the distance is in range gate
        bins. (default=None)
    dist_adjust : (bool)
        Denotes whether or not the slant distance has been adjusted for hop.
        (default=False)
    hop : (list or numpy.ndarray)
        List containing the hop for each point.  If empty, will assume 0.5-hop
        (default=list())
    hop_attr : (string)
        Name of beam attribute containing the hop (default="hop")
    model : (str or NoneType)
        Calculate virtual height using elevation (None) or model? (default=None)
        Available models
        ----------------
        IS : for standard ionopsheric scatter projection model (ignores hop)
        GS : for standard ground scatter projection model (ignores hop)
        S : for standard projection model (uses hop)
        E1 : for Chisham E-region 1/2-hop ionospheric projection model
        F1 : for Chisham F-region 1/2-hop ionospheric projection model
        F3 : for Chisham F-region 1-1/2-hop ionospheric projection model
        C : for Chisham projection model (ionospheric only, ignores hop,
            requires only total measured slant range)
    max_vh : (float)
        Maximum height for longer slant ranges in Standard model (default=400)
    
    Returns
    --------
    height : (numpy.array)
        An array of floats of the same size as the myBeam.fit.slist list,
        containing the virtual height for each range gate or NaN if a virtual
        height could not be calculated

    Notes
    --------
    Specifying a single earth radius introduces additional error into the
    resulting heights.  If the terrestrial radius at the radar location is used,
    this error is on the order of 0.01-0.1 km (much smaller than the difference
    between the real and virtual height).

    The available models are only 
    """
    import davitpy.pydarn.sdio as sdio
    import davitpy.utils.model_vheight as vhm
    s_model = ["S", "IS", "GS"]
    c_model = ["C", "F1", "F3", "E1"]

    #---------------------------------
    # Check the input
    if not isinstance(beam, sdio.radDataTypes.beamData):
        logging.error('the beam must be a beamData class')
        return None

    if not isinstance(radius, float):
        logging.error('the radius must be a float')
        return None

    if not (model is None or model in s_model or model in c_model):
        logging.error('unknown model [{:}]'.format(model))
        return None
 
    #---------------------------------
    # Load the slant range/distance
    if len(dist) == 0 or (len(elv) > 0 and len(elv) != len(dist)):
        try:
            dist = getattr(beam.fit, dist_attr)

            if dist is None:
                logging.error('no range/distance data available')
                return None
        except:
            logging.error('no range attribute [{:s}]'.format(dist_attr))
            return None
        
    if len(dist) == 0:
        logging.error("unable to calculate h' without slant range")
        return None

    #---------------------------------
    # Load elevation
    if not model in c_model:
        estr = None
        if len(elv) == 0 or  len(elv) != len(dist):
            try:
                elv = getattr(beam.fit, elv_attr)

                if model is None:
                    if elv is None:
                        logging.error('no elevation available')
                        return None
                    elif len(dist) != len(elv):
                        logging.error('unequal distance and elevation arrays')
                        return None
                else:
                    if elv is None:
                        estr = 'no elevation available'
                    elif len(dist) != len(elv):
                        estr = 'unequal distance and elevation arrays'
            except:
                estr = 'no elevation attribute [{:s}]'.format(elv_attr)
                if model is None:
                    logging.error(estr)
                    return None

        if estr is not None:
            logging.warn(estr)
            elv = [None for d in dist]
    
    if model is None and (len(elv) == 0 or len(elv) != len(dist)):
        logging.error("unable to load matching elevation and distance lists")
        return None

    #---------------------------------
    # Load hop
    if len(hop) == 0 or (len(dist) > 0 and len(hop) != len(dist)):
        estr = None
        try:
            hop = getattr(beam.fit, hop_attr)

            if hop is None:
                estr = 'no hop available'
        except:
            estr = 'no hop attribute [{:s}]'.format(hop_attr)

        if estr is not None:
            logging.warn(estr)

            if model in c_model:
                hop = [None for d in dist]
            else:
                hop = [0.5 for d in dist]

    #---------------------------------------------------------------------
    # If the hop attribute was actually the groundscatter flag, adjust
    if hop_attr == "gflg":
        hop = [1.0 if gg == 1 else 0.5 for gg in hop]

    #---------------------------------------------------------------------
    # Ensure that the range/distance (and error) are in km
    if dist_units is None:
        # Convert from range gates to km
        dist = list(5.0e-10 * scicon.c * (np.array(dist) * beam.prm.smsep
                                          + beam.prm.lagfr))
    elif dist_units is "m":
        # Convert from meters to km
        dist = [d / 1000.0 for d in dist]
    elif dist_units is not "km":
        logging.error('unknown range unit [{:s}]'.format(dist_units))
        return None

    #-----------------------------------------------------------------------
    # Cycle through the beams and elevations, calculating the virtual height
    height = np.empty(shape=(len(dist),), dtype=float) * np.nan

    for i,d in enumerate(dist):
        if model in s_model:
            # Calculate height using standard model
            hh = hop[i]
            if model != "S":
                hh = 0.5 if model == "IS" else 1.0
                
            height[i] = vhm.standard_vhm(d, adjusted_sr=dist_adjust,
                                         max_vh=max_vh, hop=hh, elv=elv[i])
        elif model in c_model:
            # Calculate height using Chisham model
            cm = None if model == "C" else model
            height[i] = vhm.chisham_vhm(d, vhmtype=cm, hop_output=False)
        elif model is None and not np.isnan(elv[i]):
            # Calculate height by assuming a spherical earth and solving the
            # law of cosines for an obtuse triangle with sides radius,
            # radius + height, and distance, where the angle between the side of
            # length distance and radius + height is equal to 90 deg - elevation
            if not dist_adjust:
                d /= hop[i] * 2.0
            
            hsqrt = np.sqrt(d**2 + radius**2 + 2.0 * d * radius
                            * np.sin(np.radians(elv[i])))
            height[i] = hsqrt - radius

    return height