예제 #1
0
def _rotate_polygon(lon, lat, lon0, lat0):
    """
    Given a polygon with vertices defined by (lon, lat), rotate the polygon
    such that the North pole of the spherical coordinates is now at (lon0,
    lat0). Therefore, to end up with a polygon centered on (lon0, lat0), the
    polygon should initially be drawn around the North pole.
    """

    # Create a representation object
    polygon = UnitSphericalRepresentation(lon=lon, lat=lat)

    # Determine rotation matrix to make it so that the circle is centered
    # on the correct longitude/latitude.
    m1 = rotation_matrix(-(0.5 * np.pi * u.radian - lat0), axis='y')
    m2 = rotation_matrix(-lon0, axis='z')
    transform_matrix = m2 * m1

    # Apply 3D rotation
    polygon = polygon.to_cartesian()

    try:
        polygon = polygon.transform(transform_matrix)
    except:  # TODO: remove once Astropy 1.1 is no longer supported
        polygon = _transform_cartesian(polygon, transform_matrix)

    polygon = UnitSphericalRepresentation.from_cartesian(polygon)

    return polygon.lon, polygon.lat
예제 #2
0
def _rotate_polygon(lon, lat, lon0, lat0):
    """
    Given a polygon with vertices defined by (lon, lat), rotate the polygon
    such that the North pole of the spherical coordinates is now at (lon0,
    lat0). Therefore, to end up with a polygon centered on (lon0, lat0), the
    polygon should initially be drawn around the North pole.
    """

    # Create a representation object
    polygon = UnitSphericalRepresentation(lon=lon, lat=lat)

    # Determine rotation matrix to make it so that the circle is centered
    # on the correct longitude/latitude.
    m1 = rotation_matrix(-(0.5 * np.pi * u.radian - lat0), axis='y')
    m2 = rotation_matrix(-lon0, axis='z')
    transform_matrix = m2 * m1

    # Apply 3D rotation
    polygon = polygon.to_cartesian()

    try:
        polygon = polygon.transform(transform_matrix)
    except:  # TODO: remove once Astropy 1.1 is no longer supported
        polygon = _transform_cartesian(polygon, transform_matrix)

    polygon = UnitSphericalRepresentation.from_cartesian(polygon)

    return polygon.lon, polygon.lat
예제 #3
0
    def remove_camera_tilt(
        self,
        coord: UnitSphericalRepresentation,
        meridian_side: MeridianSide,
    ) -> UnitSphericalRepresentation:
        """Removes the effect of camera tilt.

        This method takes a coordinate in the mount-relative spherical coordinate system and
        transforms it to where the camera would be pointed in that coordinate system if the
        camera had no tilt. This transformation is one of the steps towards determining what
        encoder positions correspond to a particular camera position.

        Note that camera tilt results in regions near the poles of the coordinate system that are
        unreachable by the mount. Specifically, input coordinates with latitudes that are within
        the camera tilt angle of either pole are unreachable. When such a coordinate is encountered
        this method will return the pole location which is the nearest reachable position.

        The formula for the longitude of the reference point was found by noting that the reference
        point has latitude 0 and its separation from the input coordinate is equal to 90 degrees
        minus the camera tilt angle. With this information and the equation for the great circle
        distance between two points on a sphere, it is possible to solve for the difference in
        longitude between the input coordinate and the reference point.

        Args:
            coord: Coordinate in the mount-relative spherical coordinate system to be transformed,
                representing the direction the camera is pointed.
            meridian_side: Side of mount meridian.

        Returns:
            Coordinate corresponding to the position the camera would be pointed if it had no tilt,
            or the nearest position that is reachable.
        """
        tilt = self.model_params.camera_tilt

        # Positions meeting these criteria are not reachable; return nearest reachable position
        if coord.lat >= 90 * u.deg - np.abs(tilt):
            return UnitSphericalRepresentation(coord.lon, 90 * u.deg)
        if coord.lat <= -(90 * u.deg - np.abs(tilt)):
            return UnitSphericalRepresentation(coord.lon, -90 * u.deg)

        # difference in longitude of coord and reference position longitude
        lon_diff = np.arccos(np.cos(90 * u.deg - tilt) / np.cos(coord.lat))

        # the tilt is in the direction of this reference position
        reference_coord = SkyCoord(
            coord.lon +
            lon_diff if meridian_side == MeridianSide.WEST else coord.lon -
            lon_diff,
            0 * u.deg,
        )
        sc = SkyCoord(coord)
        return sc.directional_offset_by(
            position_angle=sc.position_angle(reference_coord),
            separation=-self.model_params.camera_tilt).represent_as(
                UnitSphericalRepresentation)
예제 #4
0
    def encoders_to_spherical(
        self, encoder_positions: MountEncoderPositions
    ) -> Tuple[UnitSphericalRepresentation, MeridianSide]:
        """Convert from mount encoder positions to mount-relative spherical coordinates.

        The mount-relative spherical coordinate system is a defined such that the positive Z-axis,
        corresponding to a latitude angle of +90 degrees, is aligned with the physical pole of the
        mount, wherever it may be.

        Special cases: For an equatorial mount with the pole tipped up to be aligned with zenith
        the spherical coordinates returned by this function can be interpreted as topocentric
        azimuth and altitude. Alternatively, they can be interpreted as local hour angle and
        declination if used with an equatorial mount where the pole of the mount is perfectly
        aligned with the celestial pole after applying a 180-degree shift to the longitude
        coordinate.

        The details of the transformation applied here follow the conventions used by the Losmandy
        G11 mount's "physical" encoder position "pra" and "pdec". In particular, the default
        starting values of the encoders in the "counterweight down" startup position and the
        direction of axis motion corresponding to increasing encoder reading. This should work with
        other mounts as long as the "handedness" of the encoders is the same. The encoder zero
        point offsets in the mount model should take care of any difference in the startup
        positions.

        Args:
            encoder_positions: Set of mount encoder positions to be converted.

        Returns:
            Tuple where first element is the spherical coordinate, where longitude corresponds to
            mount axis 0 and latitude corresponds to mount axis 1, and the second element is the
            meridian side this position lies on.
        """

        # apply encoder offsets
        encoder_positions = MountEncoderPositions(
            Longitude(encoder_positions[0] - self.model_params.axis_0_offset),
            Longitude(encoder_positions[1] - self.model_params.axis_1_offset),
        )

        # This transformation is only correct if the mount axes are exactly orthogonal. If better
        # fidelity is required this could be replaced with a more general transformation that can
        # handle non-orthogonal axes.
        if encoder_positions[1] < 180 * u.deg:
            meridian_side = MeridianSide.EAST
            spherical_coord = UnitSphericalRepresentation(
                lon=(270 * u.deg - encoder_positions[0]),
                lat=(encoder_positions[1] - 90 * u.deg))
        else:
            meridian_side = MeridianSide.WEST
            spherical_coord = UnitSphericalRepresentation(
                lon=(90 * u.deg - encoder_positions[0]),
                lat=(270 * u.deg - encoder_positions[1]))

        return self.apply_camera_tilt(spherical_coord,
                                      meridian_side), meridian_side
예제 #5
0
def observed_to_icrs(observed_coo, icrs_frame):
    # if the data are UnitSphericalRepresentation, we can skip the distance calculations
    is_unitspherical = (isinstance(observed_coo.data,
                                   UnitSphericalRepresentation)
                        or observed_coo.cartesian.x.unit == u.one)

    usrepr = observed_coo.represent_as(UnitSphericalRepresentation)
    lon = usrepr.lon.to_value(u.radian)
    lat = usrepr.lat.to_value(u.radian)

    if isinstance(observed_coo, AltAz):
        # the 'A' indicates zen/az inputs
        coord_type = 'A'
        lat = PIOVER2 - lat
    else:
        coord_type = 'H'

    # first set up the astrometry context for ICRS<->CIRS at the observed_coo time
    astrom = erfa_astrom.get().apco(observed_coo)

    # Topocentric CIRS
    cirs_ra, cirs_dec = erfa.atoiq(coord_type, lon, lat, astrom) << u.radian
    if is_unitspherical:
        srepr = SphericalRepresentation(cirs_ra, cirs_dec, 1, copy=False)
    else:
        srepr = SphericalRepresentation(lon=cirs_ra,
                                        lat=cirs_dec,
                                        distance=observed_coo.distance,
                                        copy=False)

    # BCRS (Astrometric) direction to source
    bcrs_ra, bcrs_dec = aticq(srepr, astrom) << u.radian

    # Correct for parallax to get ICRS representation
    if is_unitspherical:
        icrs_srepr = UnitSphericalRepresentation(bcrs_ra, bcrs_dec, copy=False)
    else:
        icrs_srepr = SphericalRepresentation(lon=bcrs_ra,
                                             lat=bcrs_dec,
                                             distance=observed_coo.distance,
                                             copy=False)
        observer_icrs = CartesianRepresentation(astrom['eb'],
                                                unit=u.au,
                                                xyz_axis=-1,
                                                copy=False)
        newrepr = icrs_srepr.to_cartesian() + observer_icrs
        icrs_srepr = newrepr.represent_as(SphericalRepresentation)

    return icrs_frame.realize_frame(icrs_srepr)
예제 #6
0
파일: utils.py 프로젝트: sarahb55/glue-wwt
def center_fov(lon, lat):

    # We assume here that any non-finite and non-sensible values have already
    # been filtered out

    lon = u.Quantity(lon, u.deg, copy=False)
    lat = u.Quantity(lat, u.deg, copy=False)

    unit_sph = UnitSphericalRepresentation(lon, lat, copy=False)

    cen = unit_sph.mean()

    sep = angular_separation(lon, lat, cen.lon, cen.lat).to(u.deg).value.max()

    return cen.lon.to(u.deg).value, cen.lat.to(u.deg).value, sep
예제 #7
0
파일: utils.py 프로젝트: jsub1/glue-wwt
def center_fov(lon, lat):

    # We need to filter out any non-finite values
    keep = np.isfinite(lon) & np.isfinite(lat)
    lon, lat = lon[keep], lat[keep]

    lon = u.Quantity(lon, u.deg, copy=False)
    lat = u.Quantity(lat, u.deg, copy=False)

    unit_sph = UnitSphericalRepresentation(lon, lat, copy=False)

    cen = unit_sph.mean()

    sep = angular_separation(lon, lat, cen.lon, cen.lat).to(u.deg).value.max()

    return cen.lon.to(u.deg).value, cen.lat.to(u.deg).value, sep
예제 #8
0
def cirs_to_observed(cirs_coo, observed_frame):
    if (np.any(observed_frame.location != cirs_coo.location) or
            np.any(cirs_coo.obstime != observed_frame.obstime)):
        cirs_coo = cirs_coo.transform_to(CIRS(obstime=observed_frame.obstime,
                                              location=observed_frame.location))

    # if the data are UnitSphericalRepresentation, we can skip the distance calculations
    is_unitspherical = (isinstance(cirs_coo.data, UnitSphericalRepresentation) or
                        cirs_coo.cartesian.x.unit == u.one)

    # We used to do "astrometric" corrections here, but these are no longer necesssary
    # CIRS has proper topocentric behaviour
    usrepr = cirs_coo.represent_as(UnitSphericalRepresentation)
    cirs_ra = usrepr.lon.to_value(u.radian)
    cirs_dec = usrepr.lat.to_value(u.radian)
    # first set up the astrometry context for CIRS<->observed
    astrom = erfa_astrom.get().apio(observed_frame)

    if isinstance(observed_frame, AltAz):
        lon, zen, _, _, _ = erfa.atioq(cirs_ra, cirs_dec, astrom)
        lat = PIOVER2 - zen
    else:
        _, _, lon, lat, _ = erfa.atioq(cirs_ra, cirs_dec, astrom)

    if is_unitspherical:
        rep = UnitSphericalRepresentation(lat=u.Quantity(lat, u.radian, copy=False),
                                          lon=u.Quantity(lon, u.radian, copy=False),
                                          copy=False)
    else:
        # since we've transformed to CIRS at the observatory location, just use CIRS distance
        rep = SphericalRepresentation(lat=u.Quantity(lat, u.radian, copy=False),
                                      lon=u.Quantity(lon, u.radian, copy=False),
                                      distance=cirs_coo.distance,
                                      copy=False)
    return observed_frame.realize_frame(rep)
예제 #9
0
def icrs_to_altaz(icrs_coo, altaz_frame):
    # if the data are UnitSphericalRepresentation, we can skip the distance calculations
    is_unitspherical = (isinstance(icrs_coo.data, UnitSphericalRepresentation)
                        or icrs_coo.cartesian.x.unit == u.one)
    # first set up the astrometry context for ICRS<->AltAz
    astrom = erfa_astrom.get().apco(altaz_frame)

    # correct for parallax to find BCRS direction from observer (as in erfa.pmpx)
    if is_unitspherical:
        srepr = icrs_coo.spherical
    else:
        observer_icrs = CartesianRepresentation(astrom['eb'],
                                                unit=u.au,
                                                xyz_axis=-1,
                                                copy=False)
        srepr = (icrs_coo.cartesian -
                 observer_icrs).represent_as(SphericalRepresentation)

    # convert to topocentric CIRS
    cirs_ra, cirs_dec = atciqz(srepr, astrom)

    # now perform AltAz conversion
    az, zen, ha, odec, ora = erfa.atioq(cirs_ra, cirs_dec, astrom)
    alt = PIOVER2 - zen
    if is_unitspherical:
        aa_srepr = UnitSphericalRepresentation(az << u.radian,
                                               alt << u.radian,
                                               copy=False)
    else:
        aa_srepr = SphericalRepresentation(az << u.radian,
                                           alt << u.radian,
                                           srepr.distance,
                                           copy=False)
    return altaz_frame.realize_frame(aa_srepr)
예제 #10
0
def golden_spiral_grid(size):
    """Generate a grid of points on the surface of the unit sphere using the
    Fibonacci or Golden Spiral method.

    .. seealso::

        `Evenly distributing points on a sphere <https://stackoverflow.com/questions/9600801/evenly-distributing-n-points-on-a-sphere>`_

    Parameters
    ----------
    size : int
        The number of points to generate.

    Returns
    -------
    rep : `~astropy.coordinates.UnitSphericalRepresentation`
        The grid of points.
    """
    golden_r = (1 + 5**0.5) / 2

    grid = np.arange(0, size, dtype=float) + 0.5
    lon = 2*np.pi / golden_r * grid * u.rad
    lat = np.arcsin(1 - 2 * grid / size) * u.rad

    return UnitSphericalRepresentation(lon, lat)
예제 #11
0
def cirs_to_icrs(cirs_coo, icrs_frame):
    srepr = cirs_coo.represent_as(SphericalRepresentation)
    cirs_ra = srepr.lon.to_value(u.radian)
    cirs_dec = srepr.lat.to_value(u.radian)

    # set up the astrometry context for ICRS<->cirs and then convert to
    # astrometric coordinate direction
    astrom = erfa_astrom.get().apci(cirs_coo)
    i_ra, i_dec = aticq(cirs_ra, cirs_dec, astrom)

    if cirs_coo.data.get_name() == 'unitspherical' or cirs_coo.data.to_cartesian().x.unit == u.one:
        # if no distance, just use the coordinate direction to yield the
        # infinite-distance/no parallax answer
        newrep = UnitSphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False),
                                             lon=u.Quantity(i_ra, u.radian, copy=False),
                                             copy=False)
    else:
        # When there is a distance, apply the parallax/offset to the SSB as the
        # last step - ensures round-tripping with the icrs_to_cirs transform

        # the distance in intermedrep is *not* a real distance as it does not
        # include the offset back to the SSB
        intermedrep = SphericalRepresentation(lat=u.Quantity(i_dec, u.radian, copy=False),
                                              lon=u.Quantity(i_ra, u.radian, copy=False),
                                              distance=srepr.distance,
                                              copy=False)

        astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au,
                                            xyz_axis=-1, copy=False)
        newrep = intermedrep + astrom_eb

    return icrs_frame.realize_frame(newrep)
예제 #12
0
def tip_axis(coord: UnitSphericalRepresentation, axis_lon: Angle,
             rot_angle: Angle) -> UnitSphericalRepresentation:
    """Perform a rotation about an axis perpendicular to the Z-axis.

    The purpose of this rotation is to move the pole of the coordinate system from one place to
    another. For example, transforming from a coordinate system where the pole is aligned with the
    physical pole of a mount to a topocentric coordinate system where the pole is aligned with
    zenith.

    Note that this is a true rotation, and the same rotation is applied to all coordinates in the
    originating coordinate system equally. It is not equivalent to using SkyCoord
    directional_offset_by() with a fixed position angle and separation since the direction and
    magnitude of the offset depend on the value of coord.

    Args:
        coord: Coordinate to be transformed.
        axis_lon: Longitude angle of the axis of rotation.
        rot_angle: Angle of rotation.

    Returns:
        Coordinate after transformation.
    """
    rot = rotation_matrix(rot_angle,
                          axis=SkyCoord(axis_lon, 0 *
                                        u.deg).represent_as('cartesian').xyz)
    coord_cart = coord.represent_as(CartesianRepresentation)
    coord_rot_cart = coord_cart.transform(rot)
    return coord_rot_cart.represent_as(UnitSphericalRepresentation)
예제 #13
0
    def boundaries_skycoord(self, healpix_index, step):
        """
        Return the celestial coordinates of the edges of HEALPix pixels

        This returns the celestial coordinates of points along the edge of each
        HEALPIX pixel. The number of points returned for each pixel is ``4 * step``,
        so setting ``step`` to 1 returns just the corners.

        This method requires that a celestial frame was specified when
        initializing HEALPix.  If you don't know or need the celestial frame,
        you can instead use :meth:`~astropy_healpix.HEALPix.boundaries_lonlat`.

        Parameters
        ----------
        healpix_index : `~numpy.ndarray`
            1-D array of HEALPix pixels
        step : int
            The number of steps to take along each edge.

        Returns
        -------
        skycoord : :class:`~astropy.coordinates.SkyCoord`
            The celestial coordinates of the HEALPix pixel boundaries
        """
        if self.frame is None:
            raise NoFrameError("boundaries_skycoord")
        lon, lat = self.boundaries_lonlat(healpix_index, step)
        representation = UnitSphericalRepresentation(lon, lat, copy=False)
        return SkyCoord(self.frame.realize_frame(representation))
예제 #14
0
    def healpix_to_skycoord(self, healpix_index, dx=None, dy=None):
        """
        Convert HEALPix indices (optionally with offsets) to celestial coordinates.

        Note that this method requires that a celestial frame was specified when
        initializing HEALPix. If you don't know or need the celestial frame, you
        can instead use :meth:`~astropy_healpix.HEALPix.healpix_to_lonlat`.

        Parameters
        ----------
        healpix_index : `~numpy.ndarray`
            1-D array of HEALPix indices
        dx, dy : `~numpy.ndarray`, optional
            1-D arrays of offsets inside the HEALPix pixel, which must be in
            the range [0:1] (0.5 is the center of the HEALPix pixels). If not
            specified, the position at the center of the pixel is used.

        Returns
        -------
        coord : :class:`~astropy.coordinates.SkyCoord`
            The resulting celestial coordinates
        """
        if self.frame is None:
            raise NoFrameError("healpix_to_skycoord")
        lon, lat = self.healpix_to_lonlat(healpix_index, dx=dx, dy=dy)
        representation = UnitSphericalRepresentation(lon, lat, copy=False)
        return SkyCoord(self.frame.realize_frame(representation))
예제 #15
0
def test_horizons_consistency_with_precision():
    """
    A test to compare at high precision against output of JPL horizons.

    Tests ephemerides, and conversions from ICRS to GCRS to TETE. We are aiming for
    better than 2 milli-arcsecond precision.

    We use the Moon since it is nearby, and moves fast in the sky so we are
    testing for parallax, proper handling of light deflection and aberration.
    """
    # JPL Horizon values for 2020_04_06 00:00 to 23:00 in 1 hour steps
    # JPL Horizons has a known offset (frame bias) of 51.02 mas in RA. We correct that here
    ra_apparent_horizons = [
        170.167332531, 170.560688674, 170.923834838, 171.271663481, 171.620188972, 171.985340827,
        172.381766539, 172.821772139, 173.314502650, 173.865422398, 174.476108551, 175.144332386,
        175.864375310, 176.627519827, 177.422655853, 178.236955730, 179.056584831, 179.867427392,
        180.655815385, 181.409252074, 182.117113814, 182.771311578, 183.366872837, 183.902395443
    ] * u.deg + 51.02376467 * u.mas
    dec_apparent_horizons = [
        10.269112037, 10.058820647, 9.837152044, 9.603724551, 9.358956528, 9.104012390, 8.840674927,
        8.571162442, 8.297917326, 8.023394488, 7.749873882, 7.479312991, 7.213246666, 6.952732614,
        6.698336823, 6.450150213, 6.207828142, 5.970645962, 5.737565957, 5.507313851, 5.278462034,
        5.049521497, 4.819038911, 4.585696512
    ] * u.deg
    with solar_system_ephemeris.set('de430'):
        loc = EarthLocation.from_geodetic(-67.787260*u.deg, -22.959748*u.deg, 5186*u.m)
        times = Time('2020-04-06 00:00') + np.arange(0, 24, 1)*u.hour
        astropy = get_body('moon', times, loc)

        apparent_frame = TETE(obstime=times, location=loc)
        astropy = astropy.transform_to(apparent_frame)
        usrepr = UnitSphericalRepresentation(ra_apparent_horizons, dec_apparent_horizons)
        horizons = apparent_frame.realize_frame(usrepr)
    assert_quantity_allclose(astropy.separation(horizons), 0*u.mas, atol=1.5*u.mas)
예제 #16
0
def boundaries(nside, pix, step=1, nest=False):
    """Drop-in replacement for healpy `~healpy.boundaries`."""
    pix = np.asarray(pix)
    if pix.ndim > 1:
        # For consistency with healpy we only support scalars or 1D arrays
        raise ValueError("Array has to be one dimensional")
    lon, lat = boundaries_lonlat(pix,
                                 step,
                                 nside,
                                 order='nested' if nest else 'ring')
    rep_sph = UnitSphericalRepresentation(lon, lat)
    rep_car = rep_sph.to_cartesian().xyz.value.swapaxes(0, 1)
    if rep_car.shape[0] == 1:
        return rep_car[0]
    else:
        return rep_car
예제 #17
0
def icrs_to_cirs(icrs_coo, cirs_frame):
    # first set up the astrometry context for ICRS<->CIRS
    astrom = erfa_astrom.get().apci(cirs_frame)

    if icrs_coo.data.get_name() == 'unitspherical' or icrs_coo.data.to_cartesian().x.unit == u.one:
        # if no distance, just do the infinite-distance/no parallax calculation
        usrepr = icrs_coo.represent_as(UnitSphericalRepresentation)
        i_ra = usrepr.lon.to_value(u.radian)
        i_dec = usrepr.lat.to_value(u.radian)
        cirs_ra, cirs_dec = atciqz(i_ra, i_dec, astrom)

        newrep = UnitSphericalRepresentation(lat=u.Quantity(cirs_dec, u.radian, copy=False),
                                             lon=u.Quantity(cirs_ra, u.radian, copy=False),
                                             copy=False)
    else:
        # When there is a distance,  we first offset for parallax to get the
        # astrometric coordinate direction and *then* run the ERFA transform for
        # no parallax/PM. This ensures reversibility and is more sensible for
        # inside solar system objects
        astrom_eb = CartesianRepresentation(astrom['eb'], unit=u.au,
                                            xyz_axis=-1, copy=False)
        newcart = icrs_coo.cartesian - astrom_eb

        srepr = newcart.represent_as(SphericalRepresentation)
        i_ra = srepr.lon.to_value(u.radian)
        i_dec = srepr.lat.to_value(u.radian)
        cirs_ra, cirs_dec = atciqz(i_ra, i_dec, astrom)

        newrep = SphericalRepresentation(lat=u.Quantity(cirs_dec, u.radian, copy=False),
                                         lon=u.Quantity(cirs_ra, u.radian, copy=False),
                                         distance=srepr.distance, copy=False)

    return cirs_frame.realize_frame(newrep)
예제 #18
0
def icrs_to_gcrs(icrs_coo, gcrs_frame):
    # first set up the astrometry context for ICRS<->GCRS. There are a few steps...
    # get the position and velocity arrays for the observatory.  Need to
    # have xyz in last dimension, and pos/vel in one-but-last.
    # (Note could use np.stack once our minimum numpy version is >=1.10.)
    obs_pv = pav2pv(
        gcrs_frame.obsgeoloc.get_xyz(xyz_axis=-1).to_value(u.m),
        gcrs_frame.obsgeovel.get_xyz(xyz_axis=-1).to_value(u.m / u.s))

    # find the position and velocity of earth
    jd1, jd2 = get_jd12(gcrs_frame.obstime, 'tdb')
    earth_pv, earth_heliocentric = prepare_earth_position_vel(
        gcrs_frame.obstime)

    # get astrometry context object, astrom.
    astrom = erfa.apcs(jd1, jd2, obs_pv, earth_pv, earth_heliocentric)

    if icrs_coo.data.get_name(
    ) == 'unitspherical' or icrs_coo.data.to_cartesian().x.unit == u.one:
        # if no distance, just do the infinite-distance/no parallax calculation
        usrepr = icrs_coo.represent_as(UnitSphericalRepresentation)
        i_ra = usrepr.lon.to_value(u.radian)
        i_dec = usrepr.lat.to_value(u.radian)
        gcrs_ra, gcrs_dec = atciqz(i_ra, i_dec, astrom)

        newrep = UnitSphericalRepresentation(lat=u.Quantity(gcrs_dec,
                                                            u.radian,
                                                            copy=False),
                                             lon=u.Quantity(gcrs_ra,
                                                            u.radian,
                                                            copy=False),
                                             copy=False)
    else:
        # When there is a distance,  we first offset for parallax to get the
        # BCRS coordinate direction and *then* run the ERFA transform for no
        # parallax/PM. This ensures reversibility and is more sensible for
        # inside solar system objects
        astrom_eb = CartesianRepresentation(astrom['eb'],
                                            unit=u.au,
                                            xyz_axis=-1,
                                            copy=False)
        newcart = icrs_coo.cartesian - astrom_eb

        srepr = newcart.represent_as(SphericalRepresentation)
        i_ra = srepr.lon.to_value(u.radian)
        i_dec = srepr.lat.to_value(u.radian)
        gcrs_ra, gcrs_dec = atciqz(i_ra, i_dec, astrom)

        newrep = SphericalRepresentation(lat=u.Quantity(gcrs_dec,
                                                        u.radian,
                                                        copy=False),
                                         lon=u.Quantity(gcrs_ra,
                                                        u.radian,
                                                        copy=False),
                                         distance=srepr.distance,
                                         copy=False)

    return gcrs_frame.realize_frame(newrep)
예제 #19
0
def gcrs_to_hcrs(gcrs_coo, hcrs_frame):

    if np.any(gcrs_coo.obstime != hcrs_frame.obstime):
        # if they GCRS obstime and HCRS obstime are not the same, we first
        # have to move to a GCRS where they are.
        frameattrs = gcrs_coo.get_frame_attr_names()
        frameattrs['obstime'] = hcrs_frame.obstime
        gcrs_coo = gcrs_coo.transform_to(GCRS(**frameattrs))

    srepr = gcrs_coo.represent_as(SphericalRepresentation)
    gcrs_ra = srepr.lon.to_value(u.radian)
    gcrs_dec = srepr.lat.to_value(u.radian)

    # set up the astrometry context for ICRS<->GCRS and then convert to ICRS
    # coordinate direction
    obs_pv = pav2pv(
        gcrs_coo.obsgeoloc.get_xyz(xyz_axis=-1).to_value(u.m),
        gcrs_coo.obsgeovel.get_xyz(xyz_axis=-1).to_value(u.m / u.s))

    jd1, jd2 = get_jd12(hcrs_frame.obstime, 'tdb')
    earth_pv, earth_heliocentric = prepare_earth_position_vel(gcrs_coo.obstime)
    astrom = erfa.apcs(jd1, jd2, obs_pv, earth_pv, earth_heliocentric)

    i_ra, i_dec = aticq(gcrs_ra, gcrs_dec, astrom)

    # convert to Quantity objects
    i_ra = u.Quantity(i_ra, u.radian, copy=False)
    i_dec = u.Quantity(i_dec, u.radian, copy=False)
    if gcrs_coo.data.get_name(
    ) == 'unitspherical' or gcrs_coo.data.to_cartesian().x.unit == u.one:
        # if no distance, just use the coordinate direction to yield the
        # infinite-distance/no parallax answer
        newrep = UnitSphericalRepresentation(lat=i_dec, lon=i_ra, copy=False)
    else:
        # When there is a distance, apply the parallax/offset to the
        # Heliocentre as the last step to ensure round-tripping with the
        # hcrs_to_gcrs transform

        # Note that the distance in intermedrep is *not* a real distance as it
        # does not include the offset back to the Heliocentre
        intermedrep = SphericalRepresentation(lat=i_dec,
                                              lon=i_ra,
                                              distance=srepr.distance,
                                              copy=False)

        # astrom['eh'] and astrom['em'] contain Sun to observer unit vector,
        # and distance, respectively. Shapes are (X) and (X,3), where (X) is the
        # shape resulting from broadcasting the shape of the times object
        # against the shape of the pv array.
        # broadcast em to eh and scale eh
        eh = astrom['eh'] * astrom['em'][..., np.newaxis]
        eh = CartesianRepresentation(eh, unit=u.au, xyz_axis=-1, copy=False)

        newrep = intermedrep.to_cartesian() + eh

    return hcrs_frame.realize_frame(newrep)
예제 #20
0
def cirs_to_altaz(cirs_coo, altaz_frame):
    if np.any(cirs_coo.obstime != altaz_frame.obstime):
        # the only frame attribute for the current CIRS is the obstime, but this
        # would need to be updated if a future change allowed specifying an
        # Earth location algorithm or something
        cirs_coo = cirs_coo.transform_to(CIRS(obstime=altaz_frame.obstime))

    # we use the same obstime everywhere now that we know they're the same
    obstime = cirs_coo.obstime

    # if the data are UnitSphericalRepresentation, we can skip the distance calculations
    is_unitspherical = (isinstance(cirs_coo.data, UnitSphericalRepresentation)
                        or cirs_coo.cartesian.x.unit == u.one)

    if is_unitspherical:
        usrepr = cirs_coo.represent_as(UnitSphericalRepresentation)
        cirs_ra = usrepr.lon.to_value(u.radian)
        cirs_dec = usrepr.lat.to_value(u.radian)
    else:
        # compute an "astrometric" ra/dec -i.e., the direction of the
        # displacement vector from the observer to the target in CIRS
        loccirs = altaz_frame.location.get_itrs(
            cirs_coo.obstime).transform_to(cirs_coo)
        diffrepr = (
            cirs_coo.cartesian -
            loccirs.cartesian).represent_as(UnitSphericalRepresentation)

        cirs_ra = diffrepr.lon.to_value(u.radian)
        cirs_dec = diffrepr.lat.to_value(u.radian)

    # first set up the astrometry context for CIRS<->AltAz
    astrom = erfa_astrom.get().apio13(altaz_frame)

    az, zen, _, _, _ = erfa.atioq(cirs_ra, cirs_dec, astrom)

    if is_unitspherical:
        rep = UnitSphericalRepresentation(lat=u.Quantity(PIOVER2 - zen,
                                                         u.radian,
                                                         copy=False),
                                          lon=u.Quantity(az,
                                                         u.radian,
                                                         copy=False),
                                          copy=False)
    else:
        # now we get the distance as the cartesian distance from the earth
        # location to the coordinate location
        locitrs = altaz_frame.location.get_itrs(obstime)
        distance = locitrs.separation_3d(cirs_coo)
        rep = SphericalRepresentation(lat=u.Quantity(PIOVER2 - zen,
                                                     u.radian,
                                                     copy=False),
                                      lon=u.Quantity(az, u.radian, copy=False),
                                      distance=distance,
                                      copy=False)
    return altaz_frame.realize_frame(rep)
예제 #21
0
def gcrs_to_icrs(gcrs_coo, icrs_frame):
    srepr = gcrs_coo.represent_as(SphericalRepresentation)
    gcrs_ra = srepr.lon.to_value(u.radian)
    gcrs_dec = srepr.lat.to_value(u.radian)

    # set up the astrometry context for ICRS<->GCRS and then convert to BCRS
    # coordinate direction
    obs_pv = pav2pv(
        gcrs_coo.obsgeoloc.get_xyz(xyz_axis=-1).to_value(u.m),
        gcrs_coo.obsgeovel.get_xyz(xyz_axis=-1).to_value(u.m / u.s))

    jd1, jd2 = get_jd12(gcrs_coo.obstime, 'tdb')

    earth_pv, earth_heliocentric = prepare_earth_position_vel(gcrs_coo.obstime)
    astrom = erfa.apcs(jd1, jd2, obs_pv, earth_pv, earth_heliocentric)

    i_ra, i_dec = aticq(gcrs_ra, gcrs_dec, astrom)

    if gcrs_coo.data.get_name(
    ) == 'unitspherical' or gcrs_coo.data.to_cartesian().x.unit == u.one:
        # if no distance, just use the coordinate direction to yield the
        # infinite-distance/no parallax answer
        newrep = UnitSphericalRepresentation(lat=u.Quantity(i_dec,
                                                            u.radian,
                                                            copy=False),
                                             lon=u.Quantity(i_ra,
                                                            u.radian,
                                                            copy=False),
                                             copy=False)
    else:
        # When there is a distance, apply the parallax/offset to the SSB as the
        # last step - ensures round-tripping with the icrs_to_gcrs transform

        # the distance in intermedrep is *not* a real distance as it does not
        # include the offset back to the SSB
        intermedrep = SphericalRepresentation(lat=u.Quantity(i_dec,
                                                             u.radian,
                                                             copy=False),
                                              lon=u.Quantity(i_ra,
                                                             u.radian,
                                                             copy=False),
                                              distance=srepr.distance,
                                              copy=False)

        astrom_eb = CartesianRepresentation(astrom['eb'],
                                            unit=u.au,
                                            xyz_axis=-1,
                                            copy=False)
        newrep = intermedrep + astrom_eb

    return icrs_frame.realize_frame(newrep)
예제 #22
0
def icrs_to_cirs(icrs_coo, cirs_frame):
    # first set up the astrometry context for ICRS<->CIRS
    jd1, jd2 = get_jd12(cirs_frame.obstime, 'tt')
    x, y, s = get_cip(jd1, jd2)
    earth_pv, earth_heliocentric = prepare_earth_position_vel(
        cirs_frame.obstime)
    # erfa.apci requests TDB but TT can be used instead of TDB without any significant impact on accuracy
    astrom = erfa.apci(jd1, jd2, earth_pv, earth_heliocentric, x, y, s)

    if icrs_coo.data.get_name(
    ) == 'unitspherical' or icrs_coo.data.to_cartesian().x.unit == u.one:
        # if no distance, just do the infinite-distance/no parallax calculation
        usrepr = icrs_coo.represent_as(UnitSphericalRepresentation)
        i_ra = usrepr.lon.to_value(u.radian)
        i_dec = usrepr.lat.to_value(u.radian)
        cirs_ra, cirs_dec = atciqz(i_ra, i_dec, astrom)

        newrep = UnitSphericalRepresentation(lat=u.Quantity(cirs_dec,
                                                            u.radian,
                                                            copy=False),
                                             lon=u.Quantity(cirs_ra,
                                                            u.radian,
                                                            copy=False),
                                             copy=False)
    else:
        # When there is a distance,  we first offset for parallax to get the
        # astrometric coordinate direction and *then* run the ERFA transform for
        # no parallax/PM. This ensures reversibility and is more sensible for
        # inside solar system objects
        astrom_eb = CartesianRepresentation(astrom['eb'],
                                            unit=u.au,
                                            xyz_axis=-1,
                                            copy=False)
        newcart = icrs_coo.cartesian - astrom_eb

        srepr = newcart.represent_as(SphericalRepresentation)
        i_ra = srepr.lon.to_value(u.radian)
        i_dec = srepr.lat.to_value(u.radian)
        cirs_ra, cirs_dec = atciqz(i_ra, i_dec, astrom)

        newrep = SphericalRepresentation(lat=u.Quantity(cirs_dec,
                                                        u.radian,
                                                        copy=False),
                                         lon=u.Quantity(cirs_ra,
                                                        u.radian,
                                                        copy=False),
                                         distance=srepr.distance,
                                         copy=False)

    return cirs_frame.realize_frame(newrep)
예제 #23
0
def cirs_to_icrs(cirs_coo, icrs_frame):
    srepr = cirs_coo.represent_as(SphericalRepresentation)
    cirs_ra = srepr.lon.to_value(u.radian)
    cirs_dec = srepr.lat.to_value(u.radian)

    # set up the astrometry context for ICRS<->cirs and then convert to
    # astrometric coordinate direction
    jd1, jd2 = get_jd12(cirs_coo.obstime, 'tt')
    x, y, s = get_cip(jd1, jd2)
    earth_pv, earth_heliocentric = prepare_earth_position_vel(cirs_coo.obstime)
    # erfa.apci requests TDB but TT can be used instead of TDB without any significant impact on accuracy
    astrom = erfa.apci(jd1, jd2, earth_pv, earth_heliocentric, x, y, s)
    i_ra, i_dec = aticq(cirs_ra, cirs_dec, astrom)

    if cirs_coo.data.get_name(
    ) == 'unitspherical' or cirs_coo.data.to_cartesian().x.unit == u.one:
        # if no distance, just use the coordinate direction to yield the
        # infinite-distance/no parallax answer
        newrep = UnitSphericalRepresentation(lat=u.Quantity(i_dec,
                                                            u.radian,
                                                            copy=False),
                                             lon=u.Quantity(i_ra,
                                                            u.radian,
                                                            copy=False),
                                             copy=False)
    else:
        # When there is a distance, apply the parallax/offset to the SSB as the
        # last step - ensures round-tripping with the icrs_to_cirs transform

        # the distance in intermedrep is *not* a real distance as it does not
        # include the offset back to the SSB
        intermedrep = SphericalRepresentation(lat=u.Quantity(i_dec,
                                                             u.radian,
                                                             copy=False),
                                              lon=u.Quantity(i_ra,
                                                             u.radian,
                                                             copy=False),
                                              distance=srepr.distance,
                                              copy=False)

        astrom_eb = CartesianRepresentation(astrom['eb'],
                                            unit=u.au,
                                            xyz_axis=-1,
                                            copy=False)
        newrep = intermedrep + astrom_eb

    return icrs_frame.realize_frame(newrep)
예제 #24
0
def uniform_spherical_random_surface(size=1):
    """Generate a random sampling of points on the surface of the unit sphere.

    Parameters
    ----------
    size : int
        The number of points to generate.

    Returns
    -------
    rep : `~astropy.coordinates.UnitSphericalRepresentation`
        The random points.
    """

    rng = np.random  # can maybe switch to this being an input later - see #11628

    lon = rng.uniform(0, 2*np.pi, size) * u.rad
    lat = np.arcsin(rng.uniform(-1, 1, size=size)) * u.rad

    return UnitSphericalRepresentation(lon, lat)
예제 #25
0
def ang2vec(theta, phi, lonlat=False):
    """Drop-in replacement for healpy `~healpy.pixelfunc.ang2vec`."""
    lon, lat = _healpy_to_lonlat(theta, phi, lonlat=lonlat)
    rep_sph = UnitSphericalRepresentation(lon, lat)
    rep_car = rep_sph.represent_as(CartesianRepresentation)
    return rep_car.xyz.value
예제 #26
0

def _sun_earth_icrf(time):
    """
    Return the Sun-Earth vector for ICRF-based frames.
    """
    sun_pos_icrs = get_body_barycentric('sun', time)
    earth_pos_icrs = get_body_barycentric('earth', time)
    return earth_pos_icrs - sun_pos_icrs


# The Sun's north pole is oriented RA=286.13 deg, dec=63.87 deg in ICRS, and thus HCRS as well
# (See Archinal et al. 2011,
#   "Report of the IAU Working Group on Cartographic Coordinates and Rotational Elements: 2009")
# The orientation of the north pole in ICRS/HCRS is assumed to be constant in time
_SOLAR_NORTH_POLE_HCRS = UnitSphericalRepresentation(lon=constants.get('alpha_0'),
                                                     lat=constants.get('delta_0'))


# Calculate the rotation matrix to de-tilt the Sun's rotation axis to be parallel to the Z axis
_SUN_DETILT_MATRIX = _rotation_matrix_reprs_to_reprs(_SOLAR_NORTH_POLE_HCRS,
                                                     CartesianRepresentation(0, 0, 1))


def _affine_params_hcrs_to_hgs(hcrs_time, hgs_time):
    """
    Return the affine parameters (matrix and offset) from HCRS to HGS

    HGS shares the same origin (the Sun) as HCRS, but has its Z axis aligned with the Sun's
    rotation axis and its X axis aligned with the projection of the Sun-Earth vector onto the Sun's
    equatorial plane (i.e., the component of the Sun-Earth vector perpendicular to the Z axis).
    Thus, the transformation matrix is the product of the matrix to align the Z axis (by de-tilting
예제 #27
0
def test_frame_api():
    from astropy.coordinates.representation import SphericalRepresentation, \
                                 UnitSphericalRepresentation
    from astropy.coordinates.builtin_frames import ICRS, FK5
    # <--------------------Reference Frame/"Low-level" classes--------------------->
    # The low-level classes have a dual role: they act as specifiers of coordinate
    # frames and they *may* also contain data as one of the representation objects,
    # in which case they are the actual coordinate objects themselves.

    # They can always accept a representation as a first argument
    icrs = ICRS(UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg))

    # which is stored as the `data` attribute
    assert icrs.data.lat == 5 * u.deg
    assert icrs.data.lon == 8 * u.hourangle

    # Frames that require additional information like equinoxs or obstimes get them
    # as keyword parameters to the frame constructor.  Where sensible, defaults are
    # used. E.g., FK5 is almost always J2000 equinox
    fk5 = FK5(UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg))
    J2000 = time.Time('J2000')
    fk5_2000 = FK5(UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg),
                   equinox=J2000)
    assert fk5.equinox == fk5_2000.equinox

    # the information required to specify the frame is immutable
    J2001 = time.Time('J2001')
    with pytest.raises(AttributeError):
        fk5.equinox = J2001

    # Similar for the representation data.
    with pytest.raises(AttributeError):
        fk5.data = UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg)

    # There is also a class-level attribute that lists the attributes needed to
    # identify the frame.  These include attributes like `equinox` shown above.
    assert all(nm in ('equinox', 'obstime')
               for nm in fk5.get_frame_attr_names())

    # the result of `get_frame_attr_names` is called for particularly in  the
    # high-level class (discussed below) to allow round-tripping between various
    # frames.  It is also part of the public API for other similar developer /
    # advanced users' use.

    # The actual position information is accessed via the representation objects
    assert_allclose(icrs.represent_as(SphericalRepresentation).lat, 5 * u.deg)
    # shorthand for the above
    assert_allclose(icrs.spherical.lat, 5 * u.deg)
    assert icrs.cartesian.z.value > 0

    # Many frames have a "default" representation, the one in which they are
    # conventionally described, often with a special name for some of the
    # coordinates. E.g., most equatorial coordinate systems are spherical with RA and
    # Dec. This works simply as a shorthand for the longer form above

    assert_allclose(icrs.dec, 5 * u.deg)
    assert_allclose(fk5.ra, 8 * u.hourangle)

    assert icrs.representation_type == SphericalRepresentation

    # low-level classes can also be initialized with names valid for that representation
    # and frame:
    icrs_2 = ICRS(ra=8 * u.hour, dec=5 * u.deg, distance=1 * u.kpc)
    assert_allclose(icrs.ra, icrs_2.ra)

    # and these are taken as the default if keywords are not given:
    # icrs_nokwarg = ICRS(8*u.hour, 5*u.deg, distance=1*u.kpc)
    # assert icrs_nokwarg.ra == icrs_2.ra and icrs_nokwarg.dec == icrs_2.dec

    # they also are capable of computing on-sky or 3d separations from each other,
    # which will be a direct port of the existing methods:
    coo1 = ICRS(ra=0 * u.hour, dec=0 * u.deg)
    coo2 = ICRS(ra=0 * u.hour, dec=1 * u.deg)
    # `separation` is the on-sky separation
    assert coo1.separation(coo2).degree == 1.0

    # while `separation_3d` includes the 3D distance information
    coo3 = ICRS(ra=0 * u.hour, dec=0 * u.deg, distance=1 * u.kpc)
    coo4 = ICRS(ra=0 * u.hour, dec=0 * u.deg, distance=2 * u.kpc)
    assert coo3.separation_3d(coo4).kpc == 1.0

    # The next example fails because `coo1` and `coo2` don't have distances
    with pytest.raises(ValueError):
        assert coo1.separation_3d(coo2).kpc == 1.0
예제 #28
0
def cirs_to_altaz(cirs_coo, altaz_frame):
    if np.any(cirs_coo.obstime != altaz_frame.obstime):
        # the only frame attribute for the current CIRS is the obstime, but this
        # would need to be updated if a future change allowed specifying an
        # Earth location algorithm or something
        cirs_coo = cirs_coo.transform_to(CIRS(obstime=altaz_frame.obstime))

    # we use the same obstime everywhere now that we know they're the same
    obstime = cirs_coo.obstime

    # if the data are UnitSphericalRepresentation, we can skip the distance calculations
    is_unitspherical = (isinstance(cirs_coo.data, UnitSphericalRepresentation)
                        or cirs_coo.cartesian.x.unit == u.one)

    if is_unitspherical:
        usrepr = cirs_coo.represent_as(UnitSphericalRepresentation)
        cirs_ra = usrepr.lon.to_value(u.radian)
        cirs_dec = usrepr.lat.to_value(u.radian)
    else:
        # compute an "astrometric" ra/dec -i.e., the direction of the
        # displacement vector from the observer to the target in CIRS
        loccirs = altaz_frame.location.get_itrs(
            cirs_coo.obstime).transform_to(cirs_coo)
        diffrepr = (
            cirs_coo.cartesian -
            loccirs.cartesian).represent_as(UnitSphericalRepresentation)

        cirs_ra = diffrepr.lon.to_value(u.radian)
        cirs_dec = diffrepr.lat.to_value(u.radian)

    lon, lat, height = altaz_frame.location.to_geodetic('WGS84')
    xp, yp = get_polar_motion(obstime)

    # first set up the astrometry context for CIRS<->AltAz
    jd1, jd2 = get_jd12(obstime, 'utc')
    astrom = erfa.apio13(
        jd1,
        jd2,
        get_dut1utc(obstime),
        lon.to_value(u.radian),
        lat.to_value(u.radian),
        height.to_value(u.m),
        xp,
        yp,  # polar motion
        # all below are already in correct units because they are QuantityFrameAttribues
        altaz_frame.pressure.value,
        altaz_frame.temperature.value,
        altaz_frame.relative_humidity.value,
        altaz_frame.obswl.value)

    az, zen, _, _, _ = erfa.atioq(cirs_ra, cirs_dec, astrom)

    if is_unitspherical:
        rep = UnitSphericalRepresentation(lat=u.Quantity(PIOVER2 - zen,
                                                         u.radian,
                                                         copy=False),
                                          lon=u.Quantity(az,
                                                         u.radian,
                                                         copy=False),
                                          copy=False)
    else:
        # now we get the distance as the cartesian distance from the earth
        # location to the coordinate location
        locitrs = altaz_frame.location.get_itrs(obstime)
        distance = locitrs.separation_3d(cirs_coo)
        rep = SphericalRepresentation(lat=u.Quantity(PIOVER2 - zen,
                                                     u.radian,
                                                     copy=False),
                                      lon=u.Quantity(az, u.radian, copy=False),
                                      distance=distance,
                                      copy=False)
    return altaz_frame.realize_frame(rep)
예제 #29
0
    B = end_representation.to_cartesian()
    rotation_axis = A.cross(B)
    rotation_angle = -np.arccos(A.dot(B) /
                                (A.norm() * B.norm()))  # negation is required

    # This line works around some input/output quirks of Astropy's rotation_matrix()
    matrix = np.array(
        rotation_matrix(rotation_angle, rotation_axis.xyz.value.tolist()))
    return matrix


# The Sun's north pole is oriented RA=286.13 deg, dec=63.87 deg in ICRS, and thus HCRS as well
# (See Archinal et al. 2011,
#   "Report of the IAU Working Group on Cartographic Coordinates and Rotational Elements: 2009")
# The orientation of the north pole in ICRS/HCRS is assumed to be constant in time
_SOLAR_NORTH_POLE_HCRS = UnitSphericalRepresentation(lon=286.13 * u.deg,
                                                     lat=63.87 * u.deg)

# Calculate the rotation matrix to de-tilt the Sun's rotation axis to be parallel to the Z axis
_SUN_DETILT_MATRIX = _make_rotation_matrix_from_reprs(
    _SOLAR_NORTH_POLE_HCRS, CartesianRepresentation(0, 0, 1))


@frame_transform_graph.transform(DynamicMatrixTransform, HCRS,
                                 HeliographicStonyhurst)
def hcrs_to_hgs(hcrscoord, hgsframe):
    """
    Convert from HCRS to Heliographic Stonyhurst (HGS).

    HGS shares the same origin (the Sun) as HCRS, but has its Z axis aligned with the Sun's
    rotation axis and its X axis aligned with the projection of the Sun-Earth vector onto the Sun's
    equatorial plane (i.e., the component of the Sun-Earth vector perpendicular to the Z axis).
예제 #30
0
def test_representations_api():
    from astropy.coordinates.representation import SphericalRepresentation, \
        UnitSphericalRepresentation, PhysicsSphericalRepresentation, \
        CartesianRepresentation
    from astropy.coordinates import Angle, Longitude, Latitude, Distance

    # <-----------------Classes for representation of coordinate data-------------->
    # These classes inherit from a common base class and internally contain Quantity
    # objects, which are arrays (although they may act as scalars, like numpy's
    # length-0  "arrays")

    # They can be initialized with a variety of ways that make intuitive sense.
    # Distance is optional.
    UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg)
    UnitSphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg)
    SphericalRepresentation(lon=8 * u.hourangle,
                            lat=5 * u.deg,
                            distance=10 * u.kpc)

    # In the initial implementation, the lat/lon/distance arguments to the
    # initializer must be in order. A *possible* future change will be to allow
    # smarter guessing of the order.  E.g. `Latitude` and `Longitude` objects can be
    # given in any order.
    UnitSphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg))
    SphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg),
                            Distance(10, u.kpc))

    # Arrays of any of the inputs are fine
    UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg)

    # Default is to copy arrays, but optionally, it can be a reference
    UnitSphericalRepresentation(lon=[8, 9] * u.hourangle,
                                lat=[5, 6] * u.deg,
                                copy=False)

    # strings are parsed by `Latitude` and `Longitude` constructors, so no need to
    # implement parsing in the Representation classes
    UnitSphericalRepresentation(lon=Angle('2h6m3.3s'), lat=Angle('0.1rad'))

    # Or, you can give `Quantity`s with keywords, and they will be internally
    # converted to Angle/Distance
    c1 = SphericalRepresentation(lon=8 * u.hourangle,
                                 lat=5 * u.deg,
                                 distance=10 * u.kpc)

    # Can also give another representation object with the `reprobj` keyword.
    c2 = SphericalRepresentation.from_representation(c1)

    #  distance, lat, and lon typically will just match in shape
    SphericalRepresentation(lon=[8, 9] * u.hourangle,
                            lat=[5, 6] * u.deg,
                            distance=[10, 11] * u.kpc)
    # if the inputs are not the same, if possible they will be broadcast following
    # numpy's standard broadcasting rules.
    c2 = SphericalRepresentation(lon=[8, 9] * u.hourangle,
                                 lat=[5, 6] * u.deg,
                                 distance=10 * u.kpc)
    assert len(c2.distance) == 2
    # when they can't be broadcast, it is a ValueError (same as Numpy)
    with pytest.raises(ValueError):
        c2 = UnitSphericalRepresentation(lon=[8, 9, 10] * u.hourangle,
                                         lat=[5, 6] * u.deg)

    # It's also possible to pass in scalar quantity lists with mixed units. These
    # are converted to array quantities following the same rule as `Quantity`: all
    # elements are converted to match the first element's units.
    c2 = UnitSphericalRepresentation(
        lon=Angle([8 * u.hourangle, 135 * u.deg]),
        lat=Angle([5 * u.deg, (6 * np.pi / 180) * u.rad]))
    assert c2.lat.unit == u.deg and c2.lon.unit == u.hourangle
    npt.assert_almost_equal(c2.lon[1].value, 9)

    # The Quantity initializer itself can also be used to force the unit even if the
    # first element doesn't have the right unit
    lon = u.Quantity([120 * u.deg, 135 * u.deg], u.hourangle)
    lat = u.Quantity([(5 * np.pi / 180) * u.rad, 0.4 * u.hourangle], u.deg)
    c2 = UnitSphericalRepresentation(lon, lat)

    # regardless of how input, the `lat` and `lon` come out as angle/distance
    assert isinstance(c1.lat, Angle)
    assert isinstance(
        c1.lat,
        Latitude)  # `Latitude` is an `~astropy.coordinates.Angle` subclass
    assert isinstance(c1.distance, Distance)

    # but they are read-only, as representations are immutable once created
    with pytest.raises(AttributeError):
        c1.lat = Latitude(5, u.deg)
    # Note that it is still possible to modify the array in-place, but this is not
    # sanctioned by the API, as this would prevent things like caching.
    c2.lat[:] = [0] * u.deg  # possible, but NOT SUPPORTED

    # To address the fact that there are various other conventions for how spherical
    # coordinates are defined, other conventions can be included as new classes.
    # Later there may be other conventions that we implement - for now just the
    # physics convention, as it is one of the most common cases.
    _ = PhysicsSphericalRepresentation(phi=120 * u.deg,
                                       theta=85 * u.deg,
                                       r=3 * u.kpc)

    # first dimension must be length-3 if a lone `Quantity` is passed in.
    c1 = CartesianRepresentation(np.random.randn(3, 100) * u.kpc)
    assert c1.xyz.shape[0] == 3
    assert c1.xyz.unit == u.kpc
    assert c1.x.shape[0] == 100
    assert c1.y.shape[0] == 100
    assert c1.z.shape[0] == 100
    # can also give each as separate keywords
    CartesianRepresentation(x=np.random.randn(100) * u.kpc,
                            y=np.random.randn(100) * u.kpc,
                            z=np.random.randn(100) * u.kpc)
    # if the units don't match but are all distances, they will automatically be
    # converted to match `x`
    xarr, yarr, zarr = np.random.randn(3, 100)
    c1 = CartesianRepresentation(x=xarr * u.kpc,
                                 y=yarr * u.kpc,
                                 z=zarr * u.kpc)
    c2 = CartesianRepresentation(x=xarr * u.kpc, y=yarr * u.kpc, z=zarr * u.pc)
    assert c1.xyz.unit == c2.xyz.unit == u.kpc
    assert_allclose((c1.z / 1000) - c2.z, 0 * u.kpc, atol=1e-10 * u.kpc)

    # representations convert into other representations via  `represent_as`
    srep = SphericalRepresentation(lon=90 * u.deg,
                                   lat=0 * u.deg,
                                   distance=1 * u.pc)
    crep = srep.represent_as(CartesianRepresentation)
    assert_allclose(crep.x, 0 * u.pc, atol=1e-10 * u.pc)
    assert_allclose(crep.y, 1 * u.pc, atol=1e-10 * u.pc)
    assert_allclose(crep.z, 0 * u.pc, atol=1e-10 * u.pc)
예제 #31
0
def test_transform_api():
    from astropy.coordinates.representation import UnitSphericalRepresentation
    from astropy.coordinates.builtin_frames import ICRS, FK5
    from astropy.coordinates.baseframe import frame_transform_graph, BaseCoordinateFrame
    from astropy.coordinates.transformations import DynamicMatrixTransform
    # <------------------------Transformations------------------------------------->
    # Transformation functionality is the key to the whole scheme: they transform
    # low-level classes from one frame to another.

    # (used below but defined above in the API)
    fk5 = FK5(ra=8 * u.hour, dec=5 * u.deg)

    # If no data (or `None`) is given, the class acts as a specifier of a frame, but
    # without any stored data.
    J2001 = time.Time('J2001')
    fk5_J2001_frame = FK5(equinox=J2001)

    # if they do not have data, the string instead is the frame specification
    assert repr(fk5_J2001_frame) == "<FK5 Frame (equinox=J2001.000)>"

    #  Note that, although a frame object is immutable and can't have data added, it
    #  can be used to create a new object that does have data by giving the
    # `realize_frame` method a representation:
    srep = UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg)
    fk5_j2001_with_data = fk5_J2001_frame.realize_frame(srep)
    assert fk5_j2001_with_data.data is not None
    # Now `fk5_j2001_with_data` is in the same frame as `fk5_J2001_frame`, but it
    # is an actual low-level coordinate, rather than a frame without data.

    # These frames are primarily useful for specifying what a coordinate should be
    # transformed *into*, as they are used by the `transform_to` method
    # E.g., this snippet precesses the point to the new equinox
    newfk5 = fk5.transform_to(fk5_J2001_frame)
    assert newfk5.equinox == J2001

    # classes can also be given to `transform_to`, which then uses the defaults for
    # the frame information:
    samefk5 = fk5.transform_to(FK5)
    # `fk5` was initialized using default `obstime` and `equinox`, so:
    assert_allclose(samefk5.ra, fk5.ra, atol=1e-10 * u.deg)
    assert_allclose(samefk5.dec, fk5.dec, atol=1e-10 * u.deg)

    # transforming to a new frame necessarily loses framespec information if that
    # information is not applicable to the new frame.  This means transforms are not
    # always round-trippable:
    fk5_2 = FK5(ra=8 * u.hour, dec=5 * u.deg, equinox=J2001)
    ic_trans = fk5_2.transform_to(ICRS)

    # `ic_trans` does not have an `equinox`, so now when we transform back to FK5,
    # it's a *different* RA and Dec
    fk5_trans = ic_trans.transform_to(FK5)
    assert not allclose(fk5_2.ra, fk5_trans.ra, rtol=0, atol=1e-10 * u.deg)

    # But if you explicitly give the right equinox, all is fine
    fk5_trans_2 = fk5_2.transform_to(FK5(equinox=J2001))
    assert_allclose(fk5_2.ra, fk5_trans_2.ra, rtol=0, atol=1e-10 * u.deg)

    # Trying to transforming a frame with no data is of course an error:
    with pytest.raises(ValueError):
        FK5(equinox=J2001).transform_to(ICRS)

    # To actually define a new transformation, the same scheme as in the
    # 0.2/0.3 coordinates framework can be re-used - a graph of transform functions
    # connecting various coordinate classes together.  The main changes are:
    # 1) The transform functions now get the frame object they are transforming the
    #    current data into.
    # 2) Frames with additional information need to have a way to transform between
    #    objects of the same class, but with different framespecinfo values

    # An example transform function:
    class SomeNewSystem(BaseCoordinateFrame):
        pass

    @frame_transform_graph.transform(DynamicMatrixTransform, SomeNewSystem,
                                     FK5)
    def new_to_fk5(newobj, fk5frame):
        _ = newobj.obstime
        _ = fk5frame.equinox
        # ... build a *cartesian* transform matrix using `eq` that transforms from
        # the `newobj` frame as observed at `ot` to FK5 an equinox `eq`
        matrix = np.eye(3)
        return matrix