Esempio n. 1
0
 def work(self, snum):
     #
     # Random seed at each iteration
     #
     np.random.seed()
     #
     # Resample parallax and R0
     #
     if self.resample:
         plx_sample = np.random.normal(loc=self.plx, scale=self.plx_err)
         R0_sample = np.random.normal(loc=self.R0, scale=self.R0_err)
     else:
         plx_sample = self.plx
         R0_sample = self.R0
     #
     # Compute distances from parallax, catch large distances
     #
     distance = 1.0 / plx_sample  # kpc
     distance[distance > self.dist_max] = np.nan
     distance[distance < 0.0] = np.nan
     #
     # Compute Galactocentric radius
     #
     Rgal = kd_utils.calc_Rgal(self.glong, distance, R0=R0_sample)
     return (distance, Rgal)
Esempio n. 2
0
def parallax(glong, plx, dist_max=30., R0=8.34):
    """
    Compute parallax distance and Galactocentric radius for a given
    Galactic longitude and parallax.

    Parameters:
      glong : scalar or 1-D array
              Galactic longitude (deg). If it is an array, it must
              have the same size as plx.
      plx : scalar or 1-D array
            Parallax (milli-arcseconds). If it is an array, it must
            have the same size as glong.
      dist_max : scalar (optional)
                 The maximum parallax distance to compute (kpc)
      R0 : scalar (optional)
           Solar Galactocentric radius (kpc)

    Returns: output
      output["Rgal"] : scalar or 1-D array
                       Galactocentric radius (kpc).
      output["distance"] : scalar or 1-D array
                           Parallax distance (kpc).
      If glong and plx are scalars, each of these is a scalar.
      Otherwise they have shape (glong.size).

    Raises:
      ValueError : if glong and plx are not 1-D; or
                   if glong and plx are arrays and not the same size
    """
    #
    # check inputs
    #
    # convert scalar to array if necessary
    glong_inp, plx_inp = np.atleast_1d(glong, plx)
    # check shape of inputs
    if glong_inp.ndim != 1 or plx_inp.ndim != 1:
        raise ValueError("glong and plx must be 1-D")
    if glong_inp.size != plx_inp.size:
        raise ValueError("glong and plx must have same size")
    # ensure range [0,360) degrees
    fix_glong = glong_inp % 360.
    #
    # Compute distances
    #
    distance = np.array(1./plx_inp) # kpc
    # remove large distances
    distance[distance > dist_max] = np.nan
    #
    # Compute Galactocentric radius
    #
    Rgal = np.array([kd_utils.calc_Rgal(l,d,R0=R0)
                     for l,d in zip(fix_glong, distance)])
    #
    # Convert back to scalars if necessary
    #
    if len(fix_glong) == 1:
        return {"Rgal":Rgal[0], "distance":distance[0]}
    else:
        return {"Rgal":Rgal, "distance":distance}
Esempio n. 3
0
def calc_gcen_coords(glong, glat, dist, R0=__R0,
                     Zsun=__Zsun, roll=__roll, use_Zsunroll=True):
    """
    Calculate galactocentric Cartesian coordinates from
    galactic longitudes, latitudes, and distances from the Sun.

    Parameters:
      glong, glat :: scalar or array of scalars
        Galactic longitude and latitude (deg)

      dist :: scalar or array of scalars
        line-of-sight distance (kpc)

      R0 :: scalar or array of scalars (optional)
        Galactocentric radius of the Sun

      Zsun :: scalar (optional)
        Height of Sun above galactic midplane (pc)

      roll :: scalar (optional)
        Roll angle relative to b=0 (deg)

      use_Zsunroll :: boolean (optional)
        If True, include Zsun and roll into calculation

    Returns: x, y, Rgal, cos_az, sin_az
      x, y :: scalars or arrays of scalars
        Galactocentric Cartesian positions (kpc). Sun is on -x-axis.

      Rgal :: scalar or array of scalars
        Galactocentric cylindrical radius (kpc)

      cos_az, sin_az :: scalar or array of scalars
        Cosine and sine of Galactocentric azimuth (rad)
    """
    glong, glat, dist = np.atleast_1d(glong, glat, dist)
    if np.shape(glong) != np.shape(dist):
        glong = np.array([glong, ] * len(dist))
    Rgal = kd_utils.calc_Rgal(
        glong, glat, dist, R0=R0,
        Zsun=Zsun, roll=roll, use_Zsunroll=use_Zsunroll)
    Rgal[Rgal < 1.0e-6] = 1.0e-6  # Catch small Rgal
    az = kd_utils.calc_az(
        glong, glat, dist, R0=R0,
        Zsun=Zsun, roll=roll, use_Zsunroll=use_Zsunroll)
    cos_az = np.cos(np.deg2rad(az))
    sin_az = np.sin(np.deg2rad(az))

    x = Rgal * -cos_az
    y = Rgal * sin_az

    return x, y, Rgal, cos_az, sin_az
Esempio n. 4
0
def calc_vlsr(glong,
              glat,
              dist,
              a1=__a1,
              a2=__a2,
              a3=__a3,
              R0=__R0,
              theta0=__theta0):
    """
    Return the LSR velocity at a given Galactic longitude and
    line-of-sight distance.

    Parameters:
      glong, glat :: scalar or array of scalars
        Galactic longitude and latitude (deg).

      dist :: scalar or array of scalars
        line-of-sight distance (kpc).

      a1, a2, a3 :: scalars (optional)
        Brand rotation curve parameters

      R0, theta0 :: scalars (optional)
        Solar Galactocentric radius (kpc) and circular orbit speed at
        R0 (km/s)

    Returns: vlsr
      vlsr :: scalar or array of scalars
        LSR velocity (km/s).
    """
    input_scalar = np.isscalar(glong) and np.isscalar(glat) and np.isscalar(
        dist)
    glong, glat, dist = np.atleast_1d(glong, glat, dist)
    #
    # Convert distance to Galactocentric radius, catch small Rgal
    #
    Rgal = kd_utils.calc_Rgal(glong, glat, dist, R0=R0)
    Rgal[Rgal < 1.0e-6] = 1.0e-6
    #
    # Rotation curve circular velocity
    #
    theta = calc_theta(Rgal, a1=a1, a2=a2, a3=a3, R0=R0, theta0=theta0)
    #
    # Now take circular velocity and convert to LSR velocity
    #
    vlsr = R0 * np.sin(np.deg2rad(glong))
    vlsr = vlsr * ((theta / Rgal) - theta0 / R0)
    if input_scalar:
        return vlsr[0]
    return vlsr
Esempio n. 5
0
 def work(self, snum):
     #
     # Random seed at each iteration
     #
     np.random.seed()
     #
     # Resample parallax, R0, Zsun, and roll
     #
     if self.resample:
         plx_sample = np.random.normal(loc=self.plx, scale=self.plx_err)
         R0_sample = np.random.normal(loc=self.R0, scale=self.R0_err)
         Zsun_sample = np.random.normal(loc=self.Zsun, scale=self.Zsun_err)
         roll_sample = np.random.normal(loc=self.roll, scale=self.roll_err)
     else:
         plx_sample = self.plx
         R0_sample = self.R0
         Zsun_sample = self.Zsun
         roll_sample = self.roll
     #
     # Compute distances from parallax, catch large distances
     #
     distance = 1. / plx_sample  # kpc
     distance[distance > self.dist_max] = np.nan
     distance[distance < 0.] = np.nan
     #
     # Compute Galactocentric radius
     # ? Are we assuming latitude of zero?
     #
     Rgal = kd_utils.calc_Rgal(self.glong,
                               0.,
                               distance,
                               R0=R0_sample,
                               Zsun=Zsun_sample,
                               roll=roll_sample,
                               use_Zsunroll=True)
     return (distance, Rgal)
Esempio n. 6
0
 def work(self, snum):
     #
     # Random seed at each iteration
     #
     np.random.seed()
     #
     # Resample velocity and rotation curve parameters
     #
     if self.resample:
         params = self.rotcurve_module.resample_params(size=len(self.glong))
         velo_sample = np.random.normal(loc=self.velo, scale=self.velo_err)
     else:
         params = self.nominal_params
         velo_sample = self.velo
     #
     # Calculate LSR velocity at each (glong, distance) point
     #
     grid_vlsrs = self.rotcurve_module.calc_vlsr(self.glong_grid, self.glat,
                                                 self.dist_grid, **params)
     #
     # Get index of tangent point along each direction
     #
     tan_idxs = np.array([
         np.argmax(vlsr)
         if gl < 90.0 else np.argmin(vlsr) if gl > 270.0 else -1
         for gl, vlsr in zip(self.glong, grid_vlsrs.T)
     ])
     #
     # Get index of near and far distances along each direction
     #
     near_idxs = np.array([
         np.argmin(np.abs(vlsr[:tan_idx] - v)) if
         (tan_idx != -1
          and np.min(np.abs(vlsr[:tan_idx] - v)) < self.velo_tol) else -1
         for v, vlsr, tan_idx in zip(velo_sample, grid_vlsrs.T, tan_idxs)
     ])
     far_idxs = np.array([
         tan_idx + np.argmin(np.abs(vlsr[tan_idx:] - v)) if
         (tan_idx != -1
          and np.min(np.abs(vlsr[tan_idx:] - v)) < self.velo_tol) else
         np.argmin(np.abs(vlsr - v)) if
         (tan_idx == -1
          and np.min(np.abs(vlsr - v)) < self.velo_tol) else -1
         for v, vlsr, tan_idx in zip(velo_sample, grid_vlsrs.T, tan_idxs)
     ])
     #
     # Get VLSR of tangent point
     #
     vlsr_tan = np.array([
         vlsr[tan_idx] if tan_idx != -1 else np.nan
         for vlsr, tan_idx in zip(grid_vlsrs.T, tan_idxs)
     ])
     #
     # Get distances
     #
     tan_dists = self.dists[tan_idxs]
     tan_dists[tan_idxs == -1] = np.nan
     near_dists = self.dists[near_idxs]
     near_dists[near_idxs == -1] = np.nan
     far_dists = self.dists[far_idxs]
     far_dists[far_idxs == -1] = np.nan
     Rgal = kd_utils.calc_Rgal(self.glong,
                               self.glat,
                               far_dists.T,
                               R0=params["R0"]).T
     Rtan = kd_utils.calc_Rgal(self.glong,
                               self.glat,
                               tan_dists.T,
                               R0=params["R0"]).T
     return (tan_dists, near_dists, far_dists, vlsr_tan, Rgal, Rtan)
Esempio n. 7
0
def calc_vlsr(glong,
              glat,
              dist,
              R0=__R0,
              Usun=__Usun,
              Vsun=__Vsun,
              Wsun=__Wsun,
              Upec=__Upec,
              Vpec=__Vpec,
              a2=__a2,
              a3=__a3,
              Zsun=__Zsun,
              roll=__roll,
              peculiar=False):
    """
    Return the IAU-LSR velocity at a given Galactic longitude and
    line-of-sight distance.

    Parameters:
      glong, glat :: scalars or arrays of scalars
        Galactic longitude and latitude (deg).

      dist :: scalar or array of scalars
        line-of-sight distance (kpc).

      R0 :: scalar (optional)
        Solar Galactocentric radius (kpc)

      Usun, Vsun, Wsun, Upec, Vpec, a2, a3 :: scalars (optional)
        Reid+2019 rotation curve parameters

      Zsun :: scalar (optional)
        Height of sun above Galactic midplane (pc)

      roll :: scalar (optional)
        Roll of Galactic midplane relative to b=0 (deg)

      peculiar :: boolean (optional)
        If True, include HMSFR peculiar motion component

    Returns: vlsr
      vlsr :: scalar or array of scalars
        LSR velocity (km/s).
    """
    input_scalar = np.isscalar(glong) and np.isscalar(glat) and np.isscalar(
        dist)
    glong, glat, dist = np.atleast_1d(glong, glat, dist)
    cos_glong = np.cos(np.deg2rad(glong))
    sin_glong = np.sin(np.deg2rad(glong))
    cos_glat = np.cos(np.deg2rad(glat))
    sin_glat = np.sin(np.deg2rad(glat))
    #
    # Convert distance to Galactocentric, catch small Rgal
    #
    Rgal = kd_utils.calc_Rgal(glong, glat, dist, R0=R0)
    Rgal[Rgal < 1.e-6] = 1.e-6
    az = kd_utils.calc_az(glong, glat, dist, R0=R0)
    cos_az = np.cos(np.deg2rad(az))
    sin_az = np.sin(np.deg2rad(az))
    #
    # Rotation curve circular velocity
    #
    theta = calc_theta(Rgal, a2=a2, a3=a3, R0=R0)
    theta0 = calc_theta(R0, a2=a2, a3=a3, R0=R0)
    #
    # Add HMSFR peculiar motion
    #
    if peculiar:
        vR = -Upec
        vAz = theta + Vpec
        vZ = 0.0
    else:
        vR = 0.0
        vAz = theta
        vZ = 0.0
    vXg = -vR * cos_az + vAz * sin_az
    vYg = vR * sin_az + vAz * cos_az
    vZg = vZ
    #
    # Convert to barycentric
    #
    X = dist * cos_glat * cos_glong
    Y = dist * cos_glat * sin_glong
    Z = dist * sin_glat
    # useful constants
    sin_tilt = Zsun / 1000. / R0
    cos_tilt = np.cos(np.arcsin(sin_tilt))
    sin_roll = np.sin(np.deg2rad(roll))
    cos_roll = np.cos(np.deg2rad(roll))
    # solar peculiar motion
    vXg = vXg - Usun
    vYg = vYg - theta0 - Vsun
    vZg = vZg - Wsun
    # correct tilt and roll of Galactic midplane
    vXg1 = vXg * cos_tilt - vZg * sin_tilt
    vYg1 = vYg
    vZg1 = vXg * sin_tilt + vZg * cos_tilt
    vXh = vXg1
    vYh = vYg1 * cos_roll + vZg1 * sin_roll
    vZh = -vYg1 * sin_roll + vZg1 * cos_roll
    vbary = (X * vXh + Y * vYh + Z * vZh) / dist
    #
    # Convert to IAU-LSR
    #
    vlsr = vbary + (__Ustd * cos_glong +
                    __Vstd * sin_glong) * cos_glat + __Wstd * sin_glat
    if input_scalar:
        return vlsr[0]
    return vlsr
Esempio n. 8
0
def calc_vlsr(glong, dist, resample=False):
    """
    Return the LSR velocity at a given Galactic longitude and
    line-of-sight distance.
    If requested, resample rotation curve parameters and R0 within
    uncertainties assuming Gaussian errors.

    Parameters:
      glong : scalar or 1-D array
              Galactic longitude (deg). If it is an array, it must
              have the same size as dist.
      dist : scalar or 1-D array
             line-of-sight distance (kpc). If it is an array, it must
             have the same size as glong or glong must be a scalar.
      resample : bool (optional)
                 if True, resample rotation curve parameters within
                 uncertainties

    Returns: vlsr, params
      vlsr : scalar or 1-D array
             LSR velocity (km/s). If dist is a scalar, it
             is a scalar. Otherwise it has shape (dist.size).

      params : dict of scalars
        parameters used to calculate vlsr (useful if resample is True)
        params["R0"] : R0 used in calculation
        params["a1"] : a1 used in calculation
        params["a2"] : a2 used in calculation
        params["a3"] : a3 used in calculation

    Raises:
      ValueError : if glong or dist are not 1-D; or
                   if glong and dist are arrays and not the same size
    """
    #
    # check inputs
    #
    # convert scalar to array if necessary
    glong_inp, dist_inp = np.atleast_1d(glong, dist)
    # check shape of inputs
    if glong_inp.ndim != 1 or dist_inp.ndim != 1:
        raise ValueError("glong and dist must be 1-D")
    if glong_inp.size != 1 and glong_inp.size != dist_inp.size:
        raise ValueError("glong and dist must have same size, "
                         "or glong must be a scalar")
    #
    # Resample rotation curve parameters if necessary
    #
    if resample:
        # resample fit parameters within uncertainty
        a1 = np.random.normal(loc=__a1,scale=__a1_err)
        a2 = np.random.normal(loc=__a2,scale=__a2_err)
        a3 = np.random.normal(loc=__a3,scale=__a3_err)
        R0 = np.random.normal(loc=__R0,scale=__R0_err)
    else:
        a1 = __a1
        a2 = __a2
        a3 = __a3
        R0 = __R0
    params = {"R0":R0,"a1":a1,"a2":a2,"a3":a3}
    #
    # Convert distance to Galactocentric radius, catch places where
    # R = 0.
    #
    Rgal = kd_utils.calc_Rgal(glong_inp,dist_inp,R0=R0)
    Rgal = np.atleast_1d(Rgal)
    Rgal[Rgal < 1.e-6] = 1.e-6
    #
    # Reid rotation curve circular velocity
    #
    theta = calc_theta(Rgal,a1=a1,a2=a2,a3=a3,R0=R0)
    #
    # Now take circular velocity and convert to LSR velocity
    #
    vlsr = R0 * np.sin(np.deg2rad(glong_inp))
    vlsr = vlsr * ((theta/Rgal) - a1/R0)
    #
    # Convert back to scalar if necessary
    #
    if dist_inp.size == 1:
        return vlsr[0],params
    else:
        return vlsr,params
Esempio n. 9
0
def rotcurve_kd(glong,
                velo,
                velo_tol=1.e-1,
                rotcurve='reid14_rotcurve',
                dist_res=1.e-2,
                dist_min=0.01,
                dist_max=30.,
                resample=False):
    """
    Return the kinematic near, far, and tanget distance for a
    given Galactic longitude and LSR velocity assuming
    a given rotation curve.

    Parameters:
      glong : scalar or 1-D array
              Galactic longitude (deg). If it is an array, it must
              have the same size as velo.
      velo : scalar or 1-D array
             LSR velocity (km/s). If it is an array, it must
             have the same size as glong.
      velo_tol : scalar (optional)
                 LSR velocity tolerance to consider a match between
                 velo and rotation curve velocity
      rotcurve : string (optional)
                 rotation curve model
      dist_res : scalar (optional)
                 line-of-sight distance resolution when calculating
                 kinematic distance (kpc)
      dist_min : scalar (optional)
                 minimum line-of-sight distance when calculating
                 kinematic distance (kpc)
      dist_max : scalar (optional)
                 maximum line-of-sight distance when calculating
                 kinematic distance (kpc)
      resample : bool (optional)
                 if True, resample rotation curve parameters within
                 uncertainties
    
    Returns: output
      output["Rgal"] : scalar or 1-D array
                       Galactocentric radius (kpc).
      output["Rtan"] : scalar or 1-D array
                       Galactocentric radius of tangent point (kpc).
      output["near"] : scalar or 1-D array
                       kinematic near distance (kpc)
      output["far"] : scalar or 1-D array
                      kinematic far distance (kpc)
      output["tangent"] : scalar or 1-D array
                          kinematic tangent distance (kpc)
      output["vlsr_tangent"] : scalar or 1-D array
                               LSR velocity of tangent point (km/s)
      If glong and velo are scalars, each of these is a scalar.
      Otherwise they have shape (velo.size).

    Raises:
      ValueError : if glong and velo are not 1-D; or
                   if glong and velo are arrays and not the same size
    """
    #
    # check inputs
    #
    # convert scalar to array if necessary
    glong_inp, velo_inp = np.atleast_1d(glong, velo)
    # check shape of inputs
    if glong_inp.ndim != 1 or velo_inp.ndim != 1:
        raise ValueError("glong and velo must be 1-D")
    if glong_inp.size != velo_inp.size:
        raise ValueError("glong and velo must be same size")
    # ensure range [0,360) degrees
    fix_glong = glong_inp % 360.
    #
    # Create array of distances
    #
    dists = np.arange(dist_min, dist_max, dist_res)
    #
    # Calculate LSR velocity at each (glong,distance) point using
    # rotation curve
    #
    rotcurve_module = importlib.import_module('kd.' + rotcurve)
    vlsrs = np.zeros((fix_glong.size, dists.size))
    params = [None] * fix_glong.size
    for ind, l in enumerate(fix_glong):
        vlsr,param = \
          rotcurve_module.calc_vlsr(l,dists,resample=resample)
        vlsrs[ind] = vlsr
        params[ind] = param
    #
    # Storage for kinematic distance indicies
    #
    near_ind = np.ma.masked_all(velo_inp.size, dtype=np.int)
    far_ind = np.ma.masked_all(velo_inp.size, dtype=np.int)
    tan_ind = np.ma.masked_all(fix_glong.size, dtype=np.int)
    #
    # Find kinematic distance indicies
    #
    for i, (l, v) in enumerate(zip(fix_glong, velo_inp)):
        #
        # 2nd or 3rd quadrants
        #
        if (90. <= l <= 270.):
            #
            # far distance indicies
            #
            velo_diff = np.min(np.abs(vlsrs[i] - v))
            best_ind = np.argmin(np.abs(vlsrs[i] - v))
            if velo_diff < velo_tol:
                far_ind[i] = best_ind
        #
        # 1st or 4th quadrants
        #
        else:
            #
            # tangent distance indicies
            #
            if l <= 90.: tan_ind[i] = np.argmax(vlsrs[i])
            if l >= 270.: tan_ind[i] = np.argmin(vlsrs[i])
            # mask if tangent distance is zero
            if tan_ind[i] == 0:
                tan_ind.mask[i] = True
                continue
            #
            # near distance indicies
            #
            velo_diff = np.min(np.abs(vlsrs[i, 0:tan_ind[i]] - v))
            best_ind = np.argmin(np.abs(vlsrs[i, 0:tan_ind[i]] - v))
            if velo_diff < velo_tol:
                near_ind[i] = best_ind
            #
            # far distance indicies
            #
            velo_diff = np.min(np.abs(vlsrs[i, tan_ind[i]:] - v))
            best_ind = np.argmin(np.abs(vlsrs[i, tan_ind[i]:] - v))
            best_ind += tan_ind[i]
            if velo_diff < velo_tol:
                far_ind[i] = best_ind
    #
    # Assign distances from indicies, mask where appropriate
    #
    near_dist = np.array([
        dists[ind] if ind is not np.ma.masked else np.nan for ind in near_ind
    ])
    far_dist = np.array(
        [dists[ind] if ind is not np.ma.masked else np.nan for ind in far_ind])
    Rgal = np.array([
        kd_utils.calc_Rgal(l, d, R0=params[ind]["R0"])
        for ind, (l, d) in enumerate(zip(fix_glong, far_dist))
    ])
    tan_dist = np.array(
        [dists[ind] if ind is not np.ma.masked else np.nan for ind in tan_ind])
    Rtan = np.array([
        kd_utils.calc_Rgal(l, d, R0=params[ind]["R0"])
        for ind, (l, d) in enumerate(zip(fix_glong, tan_dist))
    ])
    #
    # Assign tangent point velocities
    #
    vlsr_tan = np.array([
        vlsrs[i][t] if t is not np.ma.masked else np.nan
        for i, t in enumerate(tan_ind)
    ])
    #
    # Convert back to scalars if necessary
    #
    if len(fix_glong) == 1:
        return {
            "Rgal": Rgal[0],
            "Rtan": Rtan[0],
            "near": near_dist[0],
            "far": far_dist[0],
            "tangent": tan_dist[0],
            "vlsr_tangent": vlsr_tan[0]
        }
    else:
        return {
            "Rgal": Rgal,
            "Rtan": Rtan,
            "near": near_dist,
            "far": far_dist,
            "tangent": tan_dist,
            "vlsr_tangent": vlsr_tan
        }
Esempio n. 10
0
 def work(self, snum):
     #
     # Random seed at each iteration
     #
     np.random.seed()
     #
     # Resample velocity and rotation curve parameters
     #
     if self.resample:
         if self.rotcurve == "wc21_rotcurve":
             params = self.rotcurve_module.resample_params(
                 self.kde,
                 size=len(self.glong),
                 nom_params=self.nominal_params,
                 use_kriging=self.use_kriging)
         else:
             params = self.rotcurve_module.resample_params(
                 size=len(self.glong))
         velo_sample = np.random.normal(loc=self.velo, scale=self.velo_err)
     else:
         params = self.nominal_params
         velo_sample = self.velo
     #
     # Calculate LSR velocity at each (glong, distance) point
     #
     if self.rotcurve == "wc21_rotcurve" or self.rotcurve == "reid19_rotcurve":
         grid_vlsrs = self.rotcurve_module.calc_vlsr(self.glong_grid,
                                                     self.glat,
                                                     self.dist_grid,
                                                     peculiar=self.peculiar,
                                                     **params)
     else:
         grid_vlsrs = self.rotcurve_module.calc_vlsr(
             self.glong_grid, self.glat, self.dist_grid, **params)
     #
     # Get index of tangent point along each direction
     #
     tan_idxs = np.array([
         np.argmax(vlsr) if l < 90. else np.argmin(vlsr) if l > 270. else -1
         for l, vlsr in zip(self.glong, grid_vlsrs.T)
     ])
     #
     # Get index of near and far distances along each direction
     #
     near_idxs = np.array([
         np.argmin(np.abs(vlsr[:tan_idx] - v)) if
         (tan_idx > 0
          and np.min(np.abs(vlsr[:tan_idx] - v)) < self.velo_tol) else -1
         for v, vlsr, tan_idx in zip(velo_sample, grid_vlsrs.T, tan_idxs)
     ])
     far_idxs = np.array([
         tan_idx + np.argmin(np.abs(vlsr[tan_idx:] - v)) if
         (tan_idx > 0
          and np.min(np.abs(vlsr[tan_idx:] - v)) < self.velo_tol) else
         np.argmin(np.abs(vlsr - v)) if
         (tan_idx == -1
          and np.min(np.abs(vlsr - v)) < self.velo_tol) else -1
         for v, vlsr, tan_idx in zip(velo_sample, grid_vlsrs.T, tan_idxs)
     ])
     #
     # Get VLSR of tangent point
     #
     vlsr_tan = np.array([
         vlsr[tan_idx] if tan_idx > 0 else np.nan
         for vlsr, tan_idx in zip(grid_vlsrs.T, tan_idxs)
     ])
     #
     # Get distances
     #
     tan_dists = self.dists[tan_idxs]
     tan_dists[tan_idxs == -1] = np.nan
     near_dists = self.dists[near_idxs]
     near_dists[near_idxs == -1] = np.nan
     far_dists = self.dists[far_idxs]
     far_dists[far_idxs == -1] = np.nan
     if self.rotcurve == "wc21_rotcurve":
         # Include (potentially resampled) Zsun and roll values
         Rgal = kd_utils.calc_Rgal(self.glong,
                                   self.glat,
                                   far_dists.T,
                                   R0=params['R0'],
                                   Zsun=params['Zsun'],
                                   roll=params['roll'],
                                   use_Zsunroll=True).T
         Rtan = kd_utils.calc_Rgal(self.glong,
                                   self.glat,
                                   tan_dists.T,
                                   R0=params['R0'],
                                   Zsun=params['Zsun'],
                                   roll=params['roll'],
                                   use_Zsunroll=True).T
     else:
         Rgal = kd_utils.calc_Rgal(self.glong,
                                   self.glat,
                                   far_dists.T,
                                   R0=params['R0']).T
         Rtan = kd_utils.calc_Rgal(self.glong,
                                   self.glat,
                                   tan_dists.T,
                                   R0=params['R0']).T
     return (tan_dists, near_dists, far_dists, vlsr_tan, Rgal, Rtan)