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)
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)
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)
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)
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
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)
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)
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)
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)
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)
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)
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
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)
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)
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)
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)
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
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)
class LSR2(LSR): obstime = TimeAttribute(default=J2000)
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
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