Example #1
0
class CelestialBodyFixed(BaseCoordinateFrame, ABC):
    """
    A coordinate frame representing the Body Fixed System of the
    Celestial Body. This is not defined for the Earth as it has its own ITRS.

    The axis orientations are:

    - x-axis: Time-dependent instantaneous direction
    - y-axis: Completes the right-handed set.
    - z-axis: Normal to the equatorial plane.

    This corresponds to "Celestial Body Fixed System" in GMAT.
    """

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    default_representation = r.CartesianRepresentation
    default_differential = r.CartesianDifferential

    def __new__(cls, *args, **kwargs):
        frame_transform_graph.transform(FunctionTransformWithFiniteDifference,
                                        cls.cb_crs, cls)(cb_crs_to_cb_fixed)
        frame_transform_graph.transform(FunctionTransformWithFiniteDifference,
                                        cls, cls.cb_crs)(cb_fixed_to_cb_crs)
        return super().__new__(cls)
Example #2
0
class CameraFrame(BaseCoordinateFrame):
    '''
    Astropy CoordinateFrame representing coordinates in the CameraPlane

    Attributes
    ----------
    pointing_direction: astropy.coordinates.AltAz
        The pointing direction of the telescope
    obstime: astropy.Time
        The timestamp of the observation, only needed to directly transform
        to Equatorial coordinates, transforming to AltAz does not need this.
    location: astropy.coordinates.EarthLocation
        The location of the observer,  only needed to directly transform
        to Equatorial coordinates, transforming to AltAz does not need this,
        default is FACT's location
    rotated: bool
        True means x points right and y points up when looking on the camera
        from the dish, which is the efinition of FACT-Tools >= 1.0 and Mars.
        False means x points up and y points left,
        which is definition in the original FACTPixelMap file.
    '''
    default_representation = PlanarRepresentation
    pointing_direction = CoordinateAttribute(frame=AltAz, default=None)
    obstime = TimeAttribute(default=None)
    location = EarthLocationAttribute(default=LOCATION)
    rotated = Attribute(default=True)
Example #3
0
class CelestialBodyTODEquatorial(BaseCoordinateFrame, ABC):
    """
    A coordinate frame representing the True-of-Date Equatorial System of the
    Celestial Body. This is not defined for the Earth as it has its own TOD

    The axis orientations are:

    - x-axis: Along the line formed by the intersection of the body equator
      and the ecliptic planes.
    - y-axis: Completes the right-handed set.
    - z-axis: Normal to the equatorial plane.

    This corresponds to "Equator System" in GMAT.
    """

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    default_representation = r.CartesianRepresentation
    default_differential = r.CartesianDifferential

    def __new__(cls, *args, **kwargs):
        frame_transform_graph.transform(DynamicMatrixTransform, cls.cb_crs,
                                        cls)(cb_crs_to_cb_tod_eq)
        frame_transform_graph.transform(DynamicMatrixTransform, cls,
                                        cls.cb_crs)(cb_tod_eq_to_cb_crs)
        return super().__new__(cls)
Example #4
0
class CelestialBodyJ2000Equatorial(BaseCoordinateFrame, ABC):
    """
    A coordinate frame representing the Equatorial System of the
    Celestial Body at J2000 Epoch. This is not defined for the Earth as it has its own
    J2000 Equatorial frame.

    The axis orientations are:

    - x-axis: Along the line formed by the intersection of the body equator and
      the x-y plane of the FK5 system, at the J2000 epoch
    - y-axis: Completes the right-handed set.
    - z-axis: Along the instantaneous body spin axis direction at the J2000 epoch

    This corresponds to "Body Inertial" in GMAT.
    """

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    default_representation = r.CartesianRepresentation
    default_differential = r.CartesianDifferential

    def __new__(cls, *args, **kwargs):
        frame_transform_graph.transform(DynamicMatrixTransform, cls.cb_crs,
                                        cls)(cb_crs_to_cb_j2000_eq)
        frame_transform_graph.transform(DynamicMatrixTransform, cls,
                                        cls.cb_crs)(cb_j2000_eq_to_cb_crs)
        return super().__new__(cls)
Example #5
0
class J2000(BaseCoordinateFrame):
    """
    A coordinate or frame in the Mean Pole and Equinox at J2000.0 Reference
    System (J2000).

    This coordinate frame is similar to GCRS but rotated by the frame bias.
    This rotation is applicable only to the
    equinox based approach, and is only an approximation. The difference
    betweenGCRS and J2000 is less than 1m for
    the Low Earth Orbit, therefore these two can be used interchangeably with
    a small error.

    This frame is for legacy applications that store data in J2000. GCRS
    should be used wherever possible.

    References
    ----------
    The definitions and conversions are from IERS Conventions 2010, Chapter 5
    [TCF1] in :doc:`References <../references>`.

    """

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    default_representation = r.CartesianRepresentation
    default_differential = r.CartesianDifferential
Example #6
0
class HeliocentricEclipticJ2000(BaseEclipticFrame):
    """
    Heliocentric ecliptic coordinates.  These origin of the coordinates are the
    center of the sun, with the x axis pointing in the direction of
    the mean equinox of J2000 and the xy-plane in the plane of the
    ecliptic of J2000 (according to the IAU 1976/1980 obliquity model).

    """
    obstime = TimeAttribute(default=DEFAULT_OBSTIME)
Example #7
0
class GeocentricSolarEcliptic(BaseEclipticFrame):
    """
    This system has its X axis towards the Sun and its Z axis perpendicular to
    the plane of the Earth's orbit around the Sun (positive North). This system
    is fixed with respect to the Earth-Sun line. It is convenient for specifying
    magnetospheric boundaries. It has also been widely adopted as the system for
    representing vector quantities in space physics databases.

    """

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)
Example #8
0
class TIRS(BaseCoordinateFrame):
    """
    A coordinate or frame in the Terrestrial Intermediate Reference System
    (TIRS).

    References
    ----------
    The definitions and conversions are from IERS Conventions 2010, Chapter 5
    [TCF1] in :doc:`References <../references>`.

    """

    default_representation = r.CartesianRepresentation
    default_differential = r.CartesianDifferential

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)
Example #9
0
class TIRS(BaseCoordinateFrame):
    """
    A coordinate or frame in the Terrestrial Intermediate Reference System (TIRS).

    References
    ----------
    The definitions and conversions are from IERS Conventions 2010, Chapter 5 [1]_.

    .. [1] IERS Conventions (2010). Gérard Petit and Brian Luzum (eds.). (IERS Technical Note ; 36) Frankfurt am Main;
        Verlag des Bundesamts für Kartographie und Geodäsie, 2010. 179 pp., ISBN 3-89888-989-6

    """

    default_representation = r.CartesianRepresentation
    default_differential = r.CartesianDifferential

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)
Example #10
0
class TelescopeFrame(BaseCoordinateFrame):
    """
    Telescope coordinate frame.

    A Frame using a UnitSphericalRepresentation.
    This is basically the same as a HorizonCoordinate, but the
    origin is at the telescope's pointing direction.

    This is used to specify coordinates in the field of view of a
    telescope that is independent of the optical properties of the telescope.

    ``fov_lon`` is aligned with azimuth and ``fov_lat`` is aligned with altitude
    of the horizontal coordinate frame as implemented in ``astropy.coordinates.AltAz``.

    This is what astropy calls a SkyOffsetCoordinate.

    Attributes
    ----------

    telescope_pointing: SkyCoord[AltAz]
        Coordinate of the telescope pointing in AltAz
    obstime: Tiem
        Observation time
    location: EarthLocation
        Location of the telescope
    """

    frame_specific_representation_info = {
        UnitSphericalRepresentation: [
            RepresentationMapping("lon", "fov_lon"),
            RepresentationMapping("lat", "fov_lat"),
        ]
    }
    default_representation = UnitSphericalRepresentation

    telescope_pointing = CoordinateAttribute(default=None, frame=AltAz)

    obstime = TimeAttribute(default=None)
    location = EarthLocationAttribute(default=None)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # make sure telescope coordinate is in range [-180°, 180°]
        if isinstance(self._data, UnitSphericalRepresentation):
            self._data.lon.wrap_angle = Angle(180, unit=u.deg)
Example #11
0
class NominalFrame(BaseCoordinateFrame):
    """
    Nominal coordinate frame.

    A Frame using a UnitSphericalRepresentation.
    This is basically the same as a HorizonCoordinate, but the
    origin is at an arbitray position in the sky.
    This is what astropy calls a SkyOffsetCoordinate

    If the telescopes are in divergent pointing, this Frame can be
    used to transform to a common system.

    Attributes
    ----------

    origin: SkyCoord[AltAz]
        Origin of this frame as a HorizonCoordinate
    obstime: Tiem
        Observation time
    location: EarthLocation
        Location of the telescope
    """

    frame_specific_representation_info = {
        UnitSphericalRepresentation: [
            RepresentationMapping("lon", "fov_lon"),
            RepresentationMapping("lat", "fov_lat"),
        ]
    }
    default_representation = UnitSphericalRepresentation

    origin = CoordinateAttribute(default=None, frame=AltAz)

    obstime = TimeAttribute(default=None)
    location = EarthLocationAttribute(default=None)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # make sure telescope coordinate is in range [-180°, 180°]
        if isinstance(self._data, UnitSphericalRepresentation):
            self._data.lon.wrap_angle = Angle(180, unit=u.deg)
Example #12
0
class TEME(BaseCoordinateFrame):
    """
    A coordinate or frame in the True Equator Mean Equinox Reference System
    (TEME).

    This frame is used as the output of the SGP4 Satellite Propagator.
    This should not be used for any other purpose.
    GCRS should be used wherever possible.

    References
    ----------
    The definitions and conversions are from Fundamentals of Astrodynamics and
    Applications 4th Ed. Section 3.7, pg 231
    [OM1]_.

    """

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    default_representation = r.CartesianRepresentation
    default_differential = r.CartesianDifferential
Example #13
0
class CameraFrame(BaseCoordinateFrame):
    """
    Camera coordinate frame.

    The camera frame is a 2d cartesian frame,
    describing position of objects in the focal plane of the telescope.

    The frame is defined as in H.E.S.S., starting at the horizon,
    the telescope is pointed to magnetic north in azimuth and then up to zenith.

    Now, x points north and y points west, so in this orientation, the
    camera coordinates line up with the CORSIKA ground coordinate system.

    MAGIC and FACT use a different camera coordinate system:
    Standing at the dish, looking at the camera, x points right, y points up.
    To transform MAGIC/FACT to ctapipe, do x' = -y, y' = -x.

    Attributes
    ----------

    focal_length : u.Quantity[length]
        Focal length of the telescope as a unit quantity (usually meters)
    rotation : u.Quantity[angle]
        Rotation angle of the camera (0 deg in most cases)
    telescope_pointing : SkyCoord[AltAz]
        Pointing direction of the telescope as SkyCoord in AltAz
    obstime : Time
        Observation time
    location : EarthLocation
        location of the telescope
    """

    default_representation = PlanarRepresentation

    focal_length = QuantityAttribute(default=0, unit=u.m)
    rotation = QuantityAttribute(default=0 * u.deg, unit=u.rad)
    telescope_pointing = CoordinateAttribute(frame=AltAz, default=None)

    obstime = TimeAttribute(default=None)
    location = EarthLocationAttribute(default=None)
Example #14
0
class ECEF(BaseCoordinateFrame):
    """Earth-Centered Earth-Fixed frame, co-moving with the Earth
    """

    default_representation = CartesianRepresentation
    """Default representation of local frames"""

    obstime = TimeAttribute(default=None)
    """The observation time"""
    def __init__(self, *args, obstime=None, **kwargs):
        """Initialisation of an ECEF frame

        Parameters
        ----------
        obstime : Time or datetime or str, optional
            The observation time
        *args
            Any representation of the frame data, e.g. x, y, and z coordinates
        **kwargs
            Any extra BaseCoordinateFrame arguments
        """
        super().__init__(*args, obstime=obstime, **kwargs)
Example #15
0
class CelestialBodyCRS(BaseCoordinateFrame, ABC):
    """
    A coordinate frame in the generic Celestial Reference System (CRS). This CRS is
    derived from ICRS by simply carrying the origin to the origin of the celestial body.

    Uses the `builtin` ephemeris to compute planetary positions in ICRS, unless the
    coordinate definition explicitly specifies an ephemeris type.
    """

    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    default_representation = r.CartesianRepresentation
    default_differential = r.CartesianDifferential

    ephemeris_type = "builtin"

    def __new__(cls, *args, **kwargs):
        frame_transform_graph.transform(AffineTransform, ICRS,
                                        cls)(icrs_to_cb_crs)
        frame_transform_graph.transform(AffineTransform, cls,
                                        ICRS)(cb_crs_to_icrs)
        return super().__new__(cls)
Example #16
0
class TelescopeFrame(BaseCoordinateFrame):
    '''
    Telescope coordinate frame.

    A Frame using a UnitSphericalRepresentation.
    This is basically the same as a HorizonCoordinate, but the
    origin is at the telescope's pointing direction.
    This is what astropy calls a SkyOffsetCoordinate

    Attributes
    ----------

    telescope_pointing: SkyCoord[HorizonFrame]
        Coordinate of the telescope pointing in HorizonFrame
    obstime: Tiem
        Observation time
    location: EarthLocation
        Location of the telescope
    '''
    frame_specific_representation_info = {
        UnitSphericalRepresentation: [
            RepresentationMapping('lon', 'delta_az'),
            RepresentationMapping('lat', 'delta_alt'),
        ]
    }
    default_representation = UnitSphericalRepresentation

    telescope_pointing = CoordinateAttribute(default=None, frame=HorizonFrame)

    obstime = TimeAttribute(default=None)
    location = EarthLocationAttribute(default=None)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # make sure telescope coordinate is in range [-180°, 180°]
        if isinstance(self._data, UnitSphericalRepresentation):
            self._data.lon.wrap_angle = Angle(180, unit=u.deg)
Example #17
0
class _PlanetaryFixed(BaseRADecFrame):
    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    def __new__(cls, *args, **kwargs):
        frame_transform_graph.transform(FunctionTransform, cls,
                                        cls.equatorial)(cls.to_equatorial)
        frame_transform_graph.transform(FunctionTransform, cls.equatorial,
                                        cls)(cls.from_equatorial)

        return super().__new__(cls)

    @staticmethod
    def to_equatorial(fixed_coo, equatorial_frame):
        # TODO replace w/ something smart (Sun/Earth special cased)
        if fixed_coo.body == Sun and type(equatorial_frame) != HCRS:
            raise ValueError(
                f"Equatorial coordinates must be of type `HCRS`, got `{type(equatorial_frame)}` instead."
            )
        elif fixed_coo.body != Sun and fixed_coo.body != equatorial_frame.body:
            raise ValueError(
                "Fixed and equatorial coordinates must have the same body if the fixed frame body is not Sun"
            )

        r = fixed_coo.cartesian

        ra, dec, W = fixed_coo.rot_elements_at_epoch(equatorial_frame.obstime)

        r = r.transform(rotation_matrix(-W, "z"))

        r_trans1 = r.transform(rotation_matrix(-(90 * u.deg - dec), "x"))
        data = r_trans1.transform(rotation_matrix(-(90 * u.deg + ra), "z"))

        return equatorial_frame.realize_frame(data)

    @staticmethod
    def from_equatorial(equatorial_coo, fixed_frame):
        # TODO replace w/ something smart (Sun/Earth special cased)
        if fixed_frame.body == Sun and type(equatorial_coo) != HCRS:
            raise ValueError(
                f"Equatorial coordinates must be of type `HCRS`, got `{type(equatorial_coo)}` instead."
            )
        elif fixed_frame.body != Sun and equatorial_coo.body != fixed_frame.body:
            raise ValueError(
                "Fixed and equatorial coordinates must have the same body if the fixed frame body is not Sun"
            )

        r = equatorial_coo.cartesian

        ra, dec, W = fixed_frame.rot_elements_at_epoch(fixed_frame.obstime)

        r_trans2 = r.transform(rotation_matrix(90 * u.deg + ra, "z"))
        r_f = r_trans2.transform(rotation_matrix(90 * u.deg - dec, "x"))
        r_f = r_f.transform(rotation_matrix(W, "z"))

        return fixed_frame.realize_frame(r_f)

    @classmethod
    def rot_elements_at_epoch(cls, epoch=J2000):
        """Provides rotational elements at epoch.

        Provides north pole of body and angle to prime meridian.

        Parameters
        ----------
        epoch : ~astropy.time.Time, optional
            Epoch, default to J2000.

        Returns
        -------
        ra, dec, W: tuple (~astropy.units.Quantity)
            Right ascension and declination of north pole, and angle of the prime meridian.

        """
        T = (epoch.tdb - J2000).to_value(u.d) / 36525
        d = (epoch.tdb - J2000).to_value(u.d)
        return cls._rot_elements_at_epoch(T, d)

    @staticmethod
    def _rot_elements_at_epoch(T, d):
        raise NotImplementedError
Example #18
0
class _PlanetaryICRS(BaseRADecFrame):
    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    def __new__(cls, *args, **kwargs):
        frame_transform_graph.transform(AffineTransform, cls, ICRS)(cls.to_icrs)
        frame_transform_graph.transform(AffineTransform, ICRS, cls)(cls.from_icrs)
        frame_transform_graph.transform(
            FunctionTransformWithFiniteDifference, cls, cls
        )(cls.self_transform)

        return super().__new__(cls)

    @staticmethod
    def to_icrs(planet_coo, _):
        # this is just an origin translation so without a distance it cannot go ahead
        if isinstance(planet_coo.data, UnitSphericalRepresentation):
            raise u.UnitsError(_NEED_ORIGIN_HINT.format(planet_coo.__class__.__name__))

        if planet_coo.data.differentials:
            bary_sun_pos, bary_sun_vel = get_body_barycentric_posvel(
                planet_coo.body.name, planet_coo.obstime
            )
            bary_sun_pos = bary_sun_pos.with_differentials(
                bary_sun_vel.represent_as(CartesianDifferential)
            )

        else:
            bary_sun_pos = get_body_barycentric(
                planet_coo.body.name, planet_coo.obstime
            )
            bary_sun_vel = None

        return None, bary_sun_pos

    @staticmethod
    def from_icrs(icrs_coo, planet_frame):
        # this is just an origin translation so without a distance it cannot go ahead
        if isinstance(icrs_coo.data, UnitSphericalRepresentation):
            raise u.UnitsError(_NEED_ORIGIN_HINT.format(icrs_coo.__class__.__name__))

        if icrs_coo.data.differentials:
            bary_sun_pos, bary_sun_vel = get_body_barycentric_posvel(
                planet_frame.body.name, planet_frame.obstime
            )
            # Beware! Negation operation is not supported for differentials
            bary_sun_pos = (-bary_sun_pos).with_differentials(
                -bary_sun_vel.represent_as(CartesianDifferential)
            )

        else:
            bary_sun_pos = -get_body_barycentric(
                planet_frame.body.name, planet_frame.obstime
            )
            bary_sun_vel = None

        return None, bary_sun_pos

    @staticmethod
    def self_transform(from_coo, to_frame):
        if np.all(from_coo.obstime == to_frame.obstime):
            return to_frame.realize_frame(from_coo.data)
        else:
            # like CIRS, we do this self-transform via ICRS
            return from_coo.transform_to(ICRS).transform_to(to_frame)
Example #19
0
 class LSR2(LSR):
     obstime = TimeAttribute(default=J2000)
Example #20
0
class ENU(BaseCoordinateFrame):
    """Local geographic frames on the Earth, oriented along cardinal directions
    """

    default_representation = CartesianRepresentation
    """Default representation of local frames"""

    location = Attribute(default=None)
    """The origin on Earth of the local frame"""

    orientation = Attribute(default=("E", "N", "U"))
    """The orientation of the local frame, as cardinal directions"""

    magnetic = Attribute(default=False)
    """When enabled, use the magnetic North instead of the geographic one"""

    obstime = TimeAttribute(default=None)
    """The observation time"""
    def __init__(self,
                 *args,
                 location=None,
                 orientation=None,
                 magnetic=False,
                 obstime=None,
                 **kwargs):
        """Initialisation of a local frame

        Parameters
        ----------
        *args
            Any representation of the frame data, e.g. x, y, and z coordinates
        location : EarthLocation
            The location on Earth of the local frame origin
        orientation : sequence of str, optional
            The cardinal directions of the x, y, and z axis (default: E, N, U)
        magnetic : boolean, optional
            Use the magnetic north instead of the geographic one (default: false)
        obstime : Time or datetime or str, optional
            The observation time
        **kwargs
            Any extra BaseCoordinateFrame arguments

        Raises
        ------
        ValueError
            The local frame configuration is not valid
        """

        # Do the base initialisation
        location = self.location if location is None else location
        orientation = self.orientation if orientation is None else orientation

        super().__init__(*args,
                         location=location,
                         orientation=orientation,
                         magnetic=magnetic,
                         obstime=obstime,
                         **kwargs)

        # Set the transform parameters
        itrs = self._location.itrs
        geo = itrs.represent_as(GeodeticRepresentation)
        latitude, longitude = geo.latitude / u.deg, geo.longitude / u.deg

        if magnetic:
            # Compute the magnetic declination
            if self._obstime is None:
                raise ValueError("Magnetic coordinates require specifying "
                                 "an observation time")
            ecef = ECEF(itrs.x, itrs.y, itrs.z, obstime=self._obstime)

            if not _HAS_GEOMAGNET:
                from ..geomagnet import field as _geomagnetic_field
            field = _geomagnetic_field(ecef)

            c = field.cartesian
            c /= c.norm()
            h = c.represent_as(HorizontalRepresentation)
            azimuth0 = (h.azimuth / u.deg).value
        else:
            azimuth0 = 0.

        def vector(name):
            tag = name[0].upper()
            if tag == "E":
                return turtle.ecef_from_horizontal(latitude, longitude,
                                                   90 + azimuth0, 0)
            elif tag == "W":
                return turtle.ecef_from_horizontal(latitude, longitude,
                                                   270 + azimuth0, 0)
            elif tag == "N":
                return turtle.ecef_from_horizontal(latitude, longitude,
                                                   azimuth0, 0)
            elif tag == "S":
                return turtle.ecef_from_horizontal(latitude, longitude,
                                                   180 + azimuth0, 0)
            elif tag == "U":
                return turtle.ecef_from_horizontal(latitude, longitude, 0, 90)
            elif tag == "D":
                return turtle.ecef_from_horizontal(latitude, longitude, 0, -90)
            else:
                raise ValueError(f"Invalid frame orientation `{name}`")

        ux = vector(self._orientation[0])
        uy = vector(self._orientation[1])
        uz = vector(self._orientation[2])

        self._basis = numpy.column_stack((ux, uy, uz))
        self._origin = itrs.cartesian
Example #21
0
class _PlanetaryFixed(BaseRADecFrame):
    obstime = TimeAttribute(default=DEFAULT_OBSTIME)

    def __new__(cls, *args, **kwargs):
        frame_transform_graph.transform(FunctionTransform, cls,
                                        cls.equatorial)(cls.to_equatorial)
        frame_transform_graph.transform(FunctionTransform, cls.equatorial,
                                        cls)(cls.from_equatorial)

        return super().__new__(cls)

    @staticmethod
    def to_equatorial(fixed_coo, equatorial_frame):
        # TODO replace w/ something smart (Sun/Earth special cased)
        # assert fixed_coo.body == equatorial_frame.body

        r = fixed_coo.cartesian.xyz

        ra, dec, W = fixed_coo.rot_elements_at_epoch(fixed_coo.obstime)
        equatorial_frame._obstime = fixed_coo.obstime

        r = transform_vector(r, -W, "z")

        r_trans1 = transform_vector(r, -(90 * u.deg - dec), "x")
        r_f = transform_vector(r_trans1, -(90 * u.deg + ra), "z")

        if fixed_coo.data.differentials:
            v = fixed_coo.differentials["s"].d_xyz
            v = transform_vector(v, -W, "z")
            v_trans1 = transform_vector(v, -(90 * u.deg - dec), "x")
            v_f = transform_vector(v_trans1, -(90 * u.deg + ra), "z")

            data = CartesianRepresentation(
                r_f, differentials=CartesianDifferential(v_f))
        else:
            data = CartesianRepresentation(r_f)
        return equatorial_frame.realize_frame(data)

    @staticmethod
    def from_equatorial(equatorial_coo, fixed_frame):
        # TODO replace w/ something smart (Sun/Earth special cased)
        # assert equatorial_coo.body == fixed_frame.body

        r = equatorial_coo.cartesian.xyz

        ra, dec, W = fixed_frame.rot_elements_at_epoch(equatorial_coo.obstime)

        r_trans2 = transform_vector(r, (90 * u.deg + ra), "z")
        r_f = transform_vector(r_trans2, (90 * u.deg - dec), "x")
        r_f = transform_vector(r_f, W, "z")

        if equatorial_coo.data.differentials:
            v = equatorial_coo.data.differentials["s"].d_xyz
            v_trans1 = transform_vector(v, (90 * u.deg + ra), "z")
            v_f = transform_vector(v_trans1, (90 * u.deg - dec), "x")
            v_f = transform_vector(v_f, W, "z")
            data = CartesianRepresentation(
                r_f, differentials=CartesianDifferential(v_f))
        else:
            data = CartesianRepresentation(r_f)
        return fixed_frame.realize_frame(data)

    @classmethod
    def rot_elements_at_epoch(cls, epoch):
        """Provides rotational elements at epoch.

        Provides north pole of body and angle to prime meridian.

        Parameters
        ----------
        epoch : ~astropy.time.Time, optional
            Epoch, default to J2000.

        Returns
        -------
        ra, dec, W: tuple (~astropy.units.Quantity)
            Right ascension and declination of north pole, and angle of the prime meridian.

        """
        T = (epoch.tdb - J2000).to(u.day).value / 36525
        d = (epoch.tdb - J2000).to(u.day).value
        return cls._rot_elements_at_epoch(T, d)

    @staticmethod
    def _rot_elements_at_epoch(T, d):
        raise NotImplementedError