class HorizonFrame(BaseCoordinateFrame): """Horizon coordinate frame. Spherical system used to describe the direction of a given position, in terms of the altitude and azimuth of the system. In practice this is functionally identical as the astropy AltAz system, but this implementation allows us to pass array pointing information, allowing us to directly transform to the Horizon Frame from the Camera system. The Following attributes are carried over from the telescope frame to allow a direct transformation from the camera frame Frame attributes: * ``array_direction`` Alt,Az direction of the array pointing * ``pointing_direction`` Alt,Az direction of the telescope pointing """ default_representation = UnitSphericalRepresentation frame_specific_representation_info = { 'spherical': [ RepresentationMapping('lon', 'az'), RepresentationMapping('lat', 'alt') ], } frame_specific_representation_info[ 'unitspherical'] = frame_specific_representation_info['spherical'] pointing_direction = Attribute(default=None) array_direction = Attribute(default=None)
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 CameraFrame(BaseCoordinateFrame): """Camera coordinate frame. The camera frame is a simple physical cartesian frame, describing the 2 dimensional position of objects in the focal plane of the telescope Most Typically this will be used to describe the positions of the pixels in the focal plane Frame attributes: * ``focal_length`` Focal length of the telescope as a unit quantity (usually meters) * ``rotation`` Rotation angle of the camera (0 deg in most cases) """ default_representation = PlanarRepresentation focal_length = Attribute(default=None) rotation = Attribute(default=0 * u.deg) pointing_direction = Attribute(default=None) array_direction = Attribute(default=None)
class NominalFrame(BaseCoordinateFrame): """Nominal coordinate frame. Cartesian system to describe the angular offset of a given position in reference to pointing direction of a nominal array pointing position. In most cases this frame is the same as the telescope frame, however in the case of divergent pointing they will differ. Event reconstruction should be performed in this system Frame attributes: * ``array_direction`` Alt,Az direction of the array pointing * ``pointing_direction`` Alt,Az direction of the telescope pointing """ default_representation = PlanarRepresentation pointing_direction = Attribute(default=None) array_direction = Attribute(default=None)
class GroundFrame(BaseCoordinateFrame): """Ground coordinate frame. The ground coordinate frame is a simple cartesian frame describing the 3 dimensional position of objects compared to the array ground level in relation to the nomial centre of the array. Typically this frame will be used for describing the position on telescopes and equipment Frame attributes: None """ default_representation = CartesianRepresentation # Pointing direction of the tilted system (alt,az), # could be the telescope pointing direction or the reconstructed shower # direction pointing_direction = Attribute(default=None)
class TiltedGroundFrame(BaseCoordinateFrame): """Tilted ground coordinate frame. The tilted ground coordinate frame is a cartesian system describing the 2 dimensional projected positions of objects in a tilted plane described by pointing_direction Typically this frame will be used for the reconstruction of the shower core position Frame attributes: * ``pointing_direction`` Alt,Az direction of the tilted reference plane """ default_representation = PlanarRepresentation # Pointing direction of the tilted system (alt,az), # could be the telescope pointing direction or the reconstructed shower # direction pointing_direction = Attribute(default=None)
class TelescopeFrame(BaseCoordinateFrame): """Telescope coordinate frame. Cartesian system to describe the angular offset of a given position in reference to pointing direction of a given telescope When pointing corrections become available they should be applied to the transformation between this frame and the camera frame Frame attributes: * ``focal_length`` Focal length of the telescope as a unit quantity (usually meters) * ``rotation`` Rotation angle of the camera (0 deg in most cases) * ``pointing_direction`` Alt,Az direction of the telescope pointing """ default_representation = PlanarRepresentation pointing_direction = Attribute(default=None)
class SPIFrame(BaseCoordinateFrame): """ INTEGRAL SPI Frame Parameters ---------- representation : `BaseRepresentation` or None A representation object or None to have no data (or use the other keywords) """ default_representation = coord.SphericalRepresentation frame_specific_representation_info = { 'spherical': [ RepresentationMapping(reprname='lon', framename='lon', defaultunit=u.degree), RepresentationMapping(reprname='lat', framename='lat', defaultunit=u.degree), RepresentationMapping(reprname='distance', framename='DIST', defaultunit=None) ], 'unitspherical': [ RepresentationMapping(reprname='lon', framename='lon', defaultunit=u.degree), RepresentationMapping(reprname='lat', framename='lat', defaultunit=u.degree) ], 'cartesian': [ RepresentationMapping(reprname='x', framename='SCX'), RepresentationMapping(reprname='y', framename='SCY'), RepresentationMapping(reprname='z', framename='SCZ') ] } # Specify frame attributes required to fully specify the frame scx_ra = Attribute(default=None) scx_dec = Attribute(default=None) scy_ra = Attribute(default=None) scy_dec = Attribute(default=None) scz_ra = Attribute(default=None) scz_dec = Attribute(default=None)
class GBMFrame(BaseCoordinateFrame): """ Fermi GBM Frame Parameters ---------- representation : `BaseRepresentation` or None A representation object or None to have no data (or use the other keywords) """ default_representation = coord.SphericalRepresentation frame_specific_representation_info = { "spherical": [ RepresentationMapping(reprname="lon", framename="lon", defaultunit=u.degree), RepresentationMapping(reprname="lat", framename="lat", defaultunit=u.degree), RepresentationMapping(reprname="distance", framename="DIST", defaultunit=None), ], "unitspherical": [ RepresentationMapping(reprname="lon", framename="lon", defaultunit=u.degree), RepresentationMapping(reprname="lat", framename="lat", defaultunit=u.degree), ], "cartesian": [ RepresentationMapping(reprname="x", framename="SCX"), RepresentationMapping(reprname="y", framename="SCY"), RepresentationMapping(reprname="z", framename="SCZ"), ], } # Specify frame attributes required to fully specify the frame sc_pos_X = Attribute(default=None) sc_pos_Y = Attribute(default=None) sc_pos_Z = Attribute(default=None) quaternion_1 = Attribute(default=None) quaternion_2 = Attribute(default=None) quaternion_3 = Attribute(default=None) quaternion_4 = Attribute(default=None) # equinox = TimeFrameAttribute(default='J2000') @staticmethod def _set_quaternion(q1, q2, q3, q4): sc_matrix = np.zeros((3, 3)) sc_matrix[0, 0] = q1**2 - q2**2 - q3**2 + q4**2 sc_matrix[0, 1] = 2.0 * (q1 * q2 + q4 * q3) sc_matrix[0, 2] = 2.0 * (q1 * q3 - q4 * q2) sc_matrix[1, 0] = 2.0 * (q1 * q2 - q4 * q3) sc_matrix[1, 1] = -(q1**2) + q2**2 - q3**2 + q4**2 sc_matrix[1, 2] = 2.0 * (q2 * q3 + q4 * q1) sc_matrix[2, 0] = 2.0 * (q1 * q3 + q4 * q2) sc_matrix[2, 1] = 2.0 * (q2 * q3 - q4 * q1) sc_matrix[2, 2] = -(q1**2) - q2**2 + q3**2 + q4**2 return sc_matrix
class Helioprojective(BaseCoordinateFrame): """ A coordinate or frame in the Helioprojective (Cartesian) system. This is a projective coordinate system centered around the observer. It is a full spherical coordinate system with position given as longitude theta_x and latitude theta_y. Parameters ---------- representation: `~astropy.coordinates.BaseRepresentation` or None. A representation object. If specified, other parameters must be in keyword form. Tx: `~astropy.coordinates.Angle` or `~astropy.units.Quantity` X-axis coordinate. Ty: `~astropy.coordinates.Angle` or `~astropy.units.Quantity` Y-axis coordinate. distance: `~astropy.units.Quantity` The radial distance from the observer to the coordinate point. obstime: SunPy Time The date and time of the observation, used to convert to heliographic carrington coordinates. observer: `~sunpy.coordinates.frames.HeliographicStonyhurst` The coordinate of the observer in the solar system. rsun: `~astropy.units.Quantity` The physical (length) radius of the Sun. Used to calculate the position of the limb for calculating distance from the observer to the coordinate. Examples -------- >>> from astropy.coordinates import SkyCoord >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(0*u.deg, 0*u.deg, 5*u.km, obstime="2010/01/01T00:00:00", ... frame="helioprojective") >>> sc # doctest: +FLOAT_CMP <SkyCoord (Helioprojective: obstime=2010-01-01 00:00:00, rsun=695508.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2010-01-01 00:00:00): (lon, lat, radius) in (deg, deg, AU) ( 0., -3.00724817, 0.98330294)>): (Tx, Ty, distance) in (arcsec, arcsec, km) ( 0., 0., 5.)> >>> sc = SkyCoord(0*u.deg, 0*u.deg, obstime="2010/01/01T00:00:00", frame="helioprojective") >>> sc # doctest: +FLOAT_CMP <SkyCoord (Helioprojective: obstime=2010-01-01 00:00:00, rsun=695508.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2010-01-01 00:00:00): (lon, lat, radius) in (deg, deg, AU) ( 0., -3.00724817, 0.98330294)>): (Tx, Ty) in arcsec ( 0., 0.)> """ default_representation = SphericalRepresentation frame_specific_representation_info = { SphericalRepresentation: [ RepresentationMapping(reprname='lon', framename='Tx', defaultunit=u.arcsec), RepresentationMapping(reprname='lat', framename='Ty', defaultunit=u.arcsec), RepresentationMapping(reprname='distance', framename='distance', defaultunit=None) ], UnitSphericalRepresentation: [ RepresentationMapping(reprname='lon', framename='Tx', defaultunit=u.arcsec), RepresentationMapping(reprname='lat', framename='Ty', defaultunit=u.arcsec) ], } obstime = TimeFrameAttributeSunPy() rsun = Attribute(default=RSUN_METERS.to(u.km)) observer = ObserverCoordinateAttribute(HeliographicStonyhurst, default="earth") def __init__(self, *args, **kwargs): wrap = kwargs.pop('wrap_longitude', True) BaseCoordinateFrame.__init__(self, *args, **kwargs) if wrap and isinstance( self._data, (UnitSphericalRepresentation, SphericalRepresentation)): self._data.lon.wrap_angle = 180 * u.deg def calculate_distance(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the disk of the Sun at the rsun radius. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.HelioProjective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D if (isinstance(self._data, SphericalRepresentation) and not (self.distance.unit is u.one and quantity_allclose(self.distance, 1 * u.one))): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the solar disk " "for observer '{}' " "without `obstime` being specified.".format( self.observer)) rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon alpha = np.arccos(np.cos(lat) * np.cos(lon)).to(lat.unit) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * np.cos(alpha) d = ((-1 * b) - np.sqrt(b**2 - 4 * c)) / 2 return self.realize_frame( SphericalRepresentation(lon=lon, lat=lat, distance=d))
class GBMFrame(BaseCoordinateFrame): """ Fermi GBM Frame Parameters ---------- representation : `BaseRepresentation` or None A representation object or None to have no data (or use the other keywords) """ default_representation = coord.SphericalRepresentation frame_specific_representation_info = { 'spherical': [ RepresentationMapping(reprname='lon', framename='lon', defaultunit=u.degree), RepresentationMapping(reprname='lat', framename='lat', defaultunit=u.degree), RepresentationMapping(reprname='distance', framename='DIST', defaultunit=None) ], 'unitspherical': [ RepresentationMapping(reprname='lon', framename='lon', defaultunit=u.degree), RepresentationMapping(reprname='lat', framename='lat', defaultunit=u.degree) ], 'cartesian': [ RepresentationMapping(reprname='x', framename='SCX'), RepresentationMapping(reprname='y', framename='SCY'), RepresentationMapping(reprname='z', framename='SCZ') ] } # Specify frame attributes required to fully specify the frame sc_pos_X = Attribute(default=None) sc_pos_Y = Attribute(default=None) sc_pos_Z = Attribute(default=None) quaternion_1 = Attribute(default=None) quaternion_2 = Attribute(default=None) quaternion_3 = Attribute(default=None) quaternion_4 = Attribute(default=None) # equinox = TimeFrameAttribute(default='J2000') @staticmethod def _set_quaternion(q1, q2, q3, q4): sc_matrix = np.zeros((3, 3)) sc_matrix[0, 0] = (q1**2 - q2**2 - q3**2 + q4**2) sc_matrix[0, 1] = 2.0 * (q1 * q2 + q4 * q3) sc_matrix[0, 2] = 2.0 * (q1 * q3 - q4 * q2) sc_matrix[1, 0] = 2.0 * (q1 * q2 - q4 * q3) sc_matrix[1, 1] = (-q1**2 + q2**2 - q3**2 + q4**2) sc_matrix[1, 2] = 2.0 * (q2 * q3 + q4 * q1) sc_matrix[2, 0] = 2.0 * (q1 * q3 + q4 * q2) sc_matrix[2, 1] = 2.0 * (q2 * q3 - q4 * q1) sc_matrix[2, 2] = (-q1**2 - q2**2 + q3**2 + q4**2) return sc_matrix
class Helioprojective(SunPyBaseCoordinateFrame): """ A coordinate or frame in the Helioprojective Cartesian (HPC) system, which is observer-based. - The origin is the location of the observer. - ``theta_x`` is the angle relative to the plane containing the Sun-observer line and the Sun's rotation axis, with positive values in the direction of the Sun's west limb. - ``theta_y`` is the angle relative to the Sun's equatorial plane, with positive values in the direction of the Sun's north pole. - ``distance`` is the Sun-observer distance. This system is frequently used in a projective form without ``distance`` specified. For observations looking very close to the center of the Sun, where the small-angle approximation is appropriate, ``theta_x`` and ``theta_y`` can be approximated as Cartesian components. A new instance can be created using the following signatures (note that if supplied, ``obstime`` and ``observer`` must be keyword arguments):: Helioprojective(theta_x, theta_y, obstime=obstime, observer=observer) Helioprojective(theta_x, theta_y, distance, obstime=obstime, observer=observer) Parameters ---------- {data} Tx : `~astropy.coordinates.Angle` or `~astropy.units.Quantity` The theta_x coordinate for this object. Not needed if ``data`` is given. Ty : `~astropy.coordinates.Angle` or `~astropy.units.Quantity` The theta_y coordinate for this object. Not needed if ``data`` is given. distance : `~astropy.units.Quantity` The distance coordinate from the observer for this object. Not needed if ``data`` is given. {observer} rsun : `~astropy.units.Quantity` The physical (length) radius of the Sun. Used to calculate the position of the limb for calculating distance from the observer to the coordinate. Defaults to the solar radius. {common} Examples -------- >>> from astropy.coordinates import SkyCoord >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(0*u.deg, 0*u.deg, 5*u.km, ... obstime="2010/01/01T00:00:00", observer="earth", frame="helioprojective") >>> sc <SkyCoord (Helioprojective: obstime=2010-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, km) (0., 0., 5.)> >>> sc = SkyCoord(0*u.deg, 0*u.deg, ... obstime="2010/01/01T00:00:00", observer="earth", frame="helioprojective") >>> sc <SkyCoord (Helioprojective: obstime=2010-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty) in arcsec (0., 0.)> >>> sc = SkyCoord(CartesianRepresentation(1*u.AU, 1e5*u.km, -2e5*u.km), ... obstime="2011/01/05T00:00:50", observer="earth", frame="helioprojective") >>> sc <SkyCoord (Helioprojective: obstime=2011-01-05T00:00:50.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU) (137.87948623, -275.75878762, 1.00000112)> """ default_representation = SphericalRepresentation frame_specific_representation_info = { SphericalRepresentation: [ RepresentationMapping(reprname='lon', framename='Tx', defaultunit=u.arcsec), RepresentationMapping(reprname='lat', framename='Ty', defaultunit=u.arcsec), RepresentationMapping(reprname='distance', framename='distance', defaultunit=None) ], UnitSphericalRepresentation: [ RepresentationMapping(reprname='lon', framename='Tx', defaultunit=u.arcsec), RepresentationMapping(reprname='lat', framename='Ty', defaultunit=u.arcsec) ], } rsun = Attribute(default=_RSUN.to(u.km)) observer = ObserverCoordinateAttribute(HeliographicStonyhurst) def make_3d(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the surface of the Sun. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.Helioprojective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D distance = self.spherical.distance if not (distance.unit is u.one and u.allclose(distance, 1 * u.one)): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the Sun " f"for observer '{self.observer}' " "without `obstime` being specified.") rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon alpha = np.arccos(np.cos(lat) * np.cos(lon)).to(lat.unit) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * np.cos(alpha) # Ingore sqrt of NaNs with np.errstate(invalid='ignore'): d = ((-1 * b) - np.sqrt(b**2 - 4 * c)) / 2 return self.realize_frame( SphericalRepresentation(lon=lon, lat=lat, distance=d)) # Support the previous name for make_3d for now calculate_distance = deprecated('1.1', name="calculate_distance", alternative="make_3d")(make_3d)
class Helioprojective(SunPyBaseCoordinateFrame): """ A coordinate or frame in the Helioprojective Cartesian (HPC) system, which is observer-based. - The origin is the location of the observer. - ``Tx`` (aka "theta_x") is the angle relative to the plane containing the Sun-observer line and the Sun's rotation axis, with positive values in the direction of the Sun's west limb. - ``Ty`` (aka "theta_y") is the angle relative to the Sun's equatorial plane, with positive values in the direction of the Sun's north pole. - ``distance`` is the Sun-observer distance. This system is frequently used in a projective form without ``distance`` specified. For observations looking very close to the center of the Sun, where the small-angle approximation is appropriate, ``Tx`` and ``Ty`` can be approximated as Cartesian components. A new instance can be created using the following signatures (note that if supplied, ``obstime`` and ``observer`` must be keyword arguments):: Helioprojective(Tx, Ty, obstime=obstime, observer=observer) Helioprojective(Tx, Ty, distance, obstime=obstime, observer=observer) Parameters ---------- {data} Tx : `~astropy.coordinates.Angle` or `~astropy.units.Quantity` The theta_x coordinate for this object. Not needed if ``data`` is given. Ty : `~astropy.coordinates.Angle` or `~astropy.units.Quantity` The theta_y coordinate for this object. Not needed if ``data`` is given. distance : `~astropy.units.Quantity` The distance coordinate from the observer for this object. Not needed if ``data`` is given. {observer} rsun : `~astropy.units.Quantity` The physical (length) radius of the Sun. Used to calculate the position of the limb for calculating distance from the observer to the coordinate. Defaults to the solar radius. {common} Examples -------- >>> from astropy.coordinates import SkyCoord >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(0*u.deg, 0*u.deg, 5*u.km, ... obstime="2010/01/01T00:00:00", observer="earth", frame="helioprojective") >>> sc <SkyCoord (Helioprojective: obstime=2010-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, km) (0., 0., 5.)> >>> sc = SkyCoord(0*u.deg, 0*u.deg, ... obstime="2010/01/01T00:00:00", observer="earth", frame="helioprojective") >>> sc <SkyCoord (Helioprojective: obstime=2010-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty) in arcsec (0., 0.)> >>> sc = SkyCoord(CartesianRepresentation(1*u.AU, 1e5*u.km, -2e5*u.km), ... obstime="2011/01/05T00:00:50", observer="earth", frame="helioprojective") >>> sc <SkyCoord (Helioprojective: obstime=2011-01-05T00:00:50.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU) (137.87948623, -275.75878762, 1.00000112)> """ frame_specific_representation_info = { SphericalRepresentation: [RepresentationMapping('lon', 'Tx', u.arcsec), RepresentationMapping('lat', 'Ty', u.arcsec), RepresentationMapping('distance', 'distance', None)], SphericalDifferential: [RepresentationMapping('d_lon', 'd_Tx', u.arcsec/u.s), RepresentationMapping('d_lat', 'd_Ty', u.arcsec/u.s), RepresentationMapping('d_distance', 'd_distance', u.km/u.s)], UnitSphericalRepresentation: [RepresentationMapping('lon', 'Tx', u.arcsec), RepresentationMapping('lat', 'Ty', u.arcsec)], } rsun = Attribute(default=_RSUN.to(u.km)) observer = ObserverCoordinateAttribute(HeliographicStonyhurst) def make_3d(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the surface of the Sun. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.Helioprojective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D distance = self.spherical.distance if not (distance.unit is u.one and u.allclose(distance, 1*u.one)): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the Sun " f"for observer '{self.observer}' " "without `obstime` being specified.") rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon # Check for the use of floats with lower precision than the native Python float if not set([lon.dtype.type, lat.dtype.type]).issubset([float, np.float64, np.longdouble]): raise SunpyUserWarning("The Helioprojective component values appear to be lower " "precision than the native Python float: " f"Tx is {lon.dtype.name}, and Ty is {lat.dtype.name}. " "To minimize precision loss, you may want to cast the values to " "`float` or `numpy.float64` via the NumPy method `.astype()`.") # Calculate the distance to the surface of the Sun using the law of cosines cos_alpha = np.cos(lat) * np.cos(lon) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * cos_alpha # Ignore sqrt of NaNs with np.errstate(invalid='ignore'): d = ((-1*b) - np.sqrt(b**2 - 4*c)) / 2 # use the "near" solution if self._spherical_screen: sphere_center = self._spherical_screen['center'].transform_to(self).cartesian c = sphere_center.norm()**2 - self._spherical_screen['radius']**2 b = -2 * sphere_center.dot(rep) # Ignore sqrt of NaNs with np.errstate(invalid='ignore'): dd = ((-1*b) + np.sqrt(b**2 - 4*c)) / 2 # use the "far" solution d = np.fmin(d, dd) if self._spherical_screen['only_off_disk'] else dd return self.realize_frame(SphericalRepresentation(lon=lon, lat=lat, distance=d)) _spherical_screen = None @classmethod @contextmanager def assume_spherical_screen(cls, center, only_off_disk=False): """ Context manager to interpret 2D coordinates as being on the inside of a spherical screen. The radius of the screen is the distance between the specified ``center`` and Sun center. This ``center`` does not have to be the same as the observer location for the coordinate frame. If they are the same, then this context manager is equivalent to assuming that the helioprojective "zeta" component is zero. This replaces the default assumption where 2D coordinates are mapped onto the surface of the Sun. Parameters ---------- center : `~astropy.coordinates.SkyCoord` The center of the spherical screen only_off_disk : `bool`, optional If `True`, apply this assumption only to off-disk coordinates, with on-disk coordinates still mapped onto the surface of the Sun. Defaults to `False`. Examples -------- .. minigallery:: sunpy.coordinates.Helioprojective.assume_spherical_screen >>> import astropy.units as u >>> from sunpy.coordinates import Helioprojective >>> h = Helioprojective(range(7)*u.arcsec*319, [0]*7*u.arcsec, ... observer='earth', obstime='2020-04-08') >>> print(h.make_3d()) <Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU) [( 0., 0., 0.99660825), ( 319., 0., 0.99687244), ( 638., 0., 0.99778472), ( 957., 0., 1.00103285), (1276., 0., nan), (1595., 0., nan), (1914., 0., nan)]> >>> with Helioprojective.assume_spherical_screen(h.observer): ... print(h.make_3d()) <Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU) [( 0., 0., 1.00125872), ( 319., 0., 1.00125872), ( 638., 0., 1.00125872), ( 957., 0., 1.00125872), (1276., 0., 1.00125872), (1595., 0., 1.00125872), (1914., 0., 1.00125872)]> >>> with Helioprojective.assume_spherical_screen(h.observer, only_off_disk=True): ... print(h.make_3d()) <Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU) [( 0., 0., 0.99660825), ( 319., 0., 0.99687244), ( 638., 0., 0.99778472), ( 957., 0., 1.00103285), (1276., 0., 1.00125872), (1595., 0., 1.00125872), (1914., 0., 1.00125872)]> """ try: old_spherical_screen = cls._spherical_screen # nominally None center_hgs = center.transform_to(HeliographicStonyhurst(obstime=center.obstime)) cls._spherical_screen = { 'center': center, 'radius': center_hgs.radius, 'only_off_disk': only_off_disk } yield finally: cls._spherical_screen = old_spherical_screen
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 Helioprojective(BaseCoordinateFrame): """ A coordinate or frame in the Helioprojective (Cartesian) system. This is a projective coordinate system centered around the observer. It is a full spherical coordinate system with position given as longitude theta_x and latitude theta_y. Parameters ---------- representation: `~astropy.coordinates.BaseRepresentation` or None. A representation object. If specified, other parameters must be in keyword form. Tx: `~astropy.coordinates.Angle` or `~astropy.units.Quantity` X-axis coordinate. Ty: `~astropy.coordinates.Angle` or `~astropy.units.Quantity` Y-axis coordinate. distance: `~astropy.units.Quantity` The radial distance from the observer to the coordinate point. obstime: SunPy Time The date and time of the observation, used to convert to heliographic carrington coordinates. observer: `~sunpy.coordinates.frames.HeliographicStonyhurst` The coordinate of the observer in the solar system. rsun: `~astropy.units.Quantity` The physical (length) radius of the Sun. Used to calculate the position of the limb for calculating distance from the observer to the coordinate. Examples -------- >>> from astropy.coordinates import SkyCoord >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(0*u.deg, 0*u.deg, 5*u.km, obstime="2010/01/01T00:00:00", ... frame="helioprojective") >>> sc <SkyCoord (HelioProjective): obstime=2010-01-01 00:00:00, D0=149597870.7 km , Tx=0.0 arcsec, Ty=0.0 arcsec, distance=5.0 km> >>> sc = SkyCoord(0*u.deg, 0*u.deg, obstime="2010/01/01T00:00:00", frame="helioprojective") >>> sc <SkyCoord (HelioProjective): obstime=2010-01-01 00:00:00, D0=149597870.7 km , Tx=0.0 arcsec, Ty=0.0 arcsec, distance=149597870.7 km> """ default_representation = SphericalWrap180Representation _frame_specific_representation_info = { 'spherical': [ RepresentationMapping('lon', 'Tx', u.arcsec), RepresentationMapping('lat', 'Ty', u.arcsec), RepresentationMapping('distance', 'distance', u.km) ], 'sphericalwrap180': [ RepresentationMapping('lon', 'Tx', u.arcsec), RepresentationMapping('lat', 'Ty', u.arcsec), RepresentationMapping('distance', 'distance', u.km) ], 'unitspherical': [ RepresentationMapping('lon', 'Tx', u.arcsec), RepresentationMapping('lat', 'Ty', u.arcsec) ], 'unitsphericalwrap180': [ RepresentationMapping('lon', 'Tx', u.arcsec), RepresentationMapping('lat', 'Ty', u.arcsec) ] } obstime = TimeFrameAttributeSunPy() rsun = Attribute(default=RSUN_METERS.to(u.km)) observer = ObserverCoordinateAttribute(HeliographicStonyhurst, default="earth") def __init__(self, *args, **kwargs): _rep_kwarg = kwargs.get('representation', None) BaseCoordinateFrame.__init__(self, *args, **kwargs) # Convert from Spherical to SphericalWrap180 # If representation was explicitly passed, do not change the rep. if not _rep_kwarg: # The base __init__ will make this a UnitSphericalRepresentation # This makes it Wrap180 instead if isinstance(self._data, UnitSphericalRepresentation): self._data = UnitSphericalWrap180Representation( lat=self._data.lat, lon=self._data.lon) self.representation = UnitSphericalWrap180Representation # Make a Spherical Wrap180 instead elif isinstance(self._data, SphericalRepresentation): self._data = SphericalWrap180Representation( lat=self._data.lat, lon=self._data.lon, distance=self._data.distance) self.representation = SphericalWrap180Representation def calculate_distance(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the disk of the Sun at the rsun radius. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.HelioProjective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D if isinstance(self._data, SphericalRepresentation): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the solar disk " "for observer '{}' " "without `obstime` being specified.".format( self.observer)) rep = self.represent_as(UnitSphericalWrap180Representation) lat, lon = rep.lat, rep.lon alpha = np.arccos(np.cos(lat) * np.cos(lon)).to(lat.unit) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * np.cos(alpha) d = ((-1 * b) - np.sqrt(b**2 - 4 * c)) / 2 return self.realize_frame( SphericalWrap180Representation(lon=lon, lat=lat, distance=d))
class Helioprojective(SunPyBaseCoordinateFrame): """ A coordinate or frame in the Helioprojective (Cartesian) system. This is a projective coordinate system centered around the observer. It is a full spherical coordinate system with position given as longitude theta_x and latitude theta_y. Parameters ---------- representation: `~astropy.coordinates.BaseRepresentation` or None. A representation object. If specified, other parameters must be in keyword form. Tx: `~astropy.coordinates.Angle` or `~astropy.units.Quantity` X-axis coordinate. Ty: `~astropy.coordinates.Angle` or `~astropy.units.Quantity` Y-axis coordinate. distance: `~astropy.units.Quantity` The radial distance from the observer to the coordinate point. obstime: SunPy Time The date and time of the observation, used to convert to heliographic carrington coordinates. observer: `~sunpy.coordinates.frames.HeliographicStonyhurst`, str The coordinate of the observer in the solar system. If you supply a string, it must be a solar system body that can be parsed by `~sunpy.coordinates.ephemeris.get_body_heliographic_stonyhurst`. rsun: `~astropy.units.Quantity` The physical (length) radius of the Sun. Used to calculate the position of the limb for calculating distance from the observer to the coordinate. Examples -------- >>> from astropy.coordinates import SkyCoord >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(0*u.deg, 0*u.deg, 5*u.km, obstime="2010/01/01T00:00:00", ... frame="helioprojective") >>> sc <SkyCoord (Helioprojective: obstime=2010-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, km) (0., 0., 5.)> >>> sc = SkyCoord(0*u.deg, 0*u.deg, obstime="2010/01/01T00:00:00", frame="helioprojective") >>> sc <SkyCoord (Helioprojective: obstime=2010-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty) in arcsec (0., 0.)> """ default_representation = SphericalRepresentation frame_specific_representation_info = { SphericalRepresentation: [ RepresentationMapping(reprname='lon', framename='Tx', defaultunit=u.arcsec), RepresentationMapping(reprname='lat', framename='Ty', defaultunit=u.arcsec), RepresentationMapping(reprname='distance', framename='distance', defaultunit=None) ], UnitSphericalRepresentation: [ RepresentationMapping(reprname='lon', framename='Tx', defaultunit=u.arcsec), RepresentationMapping(reprname='lat', framename='Ty', defaultunit=u.arcsec) ], } obstime = TimeFrameAttributeSunPy() rsun = Attribute(default=_RSUN.to(u.km)) observer = ObserverCoordinateAttribute(HeliographicStonyhurst, default="earth") def calculate_distance(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the disk of the Sun at the rsun radius. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.HelioProjective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D distance = self.spherical.distance if not (distance.unit is u.one and u.allclose(distance, 1 * u.one)): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the solar disk " "for observer '{}' " "without `obstime` being specified.".format( self.observer)) rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon alpha = np.arccos(np.cos(lat) * np.cos(lon)).to(lat.unit) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * np.cos(alpha) # Ingore sqrt of NaNs with np.errstate(invalid='ignore'): d = ((-1 * b) - np.sqrt(b**2 - 4 * c)) / 2 return self.realize_frame( SphericalRepresentation(lon=lon, lat=lat, distance=d))