def get_observer_meta(observer, rsun: (u.Mm, None)): """ Function to get observer meta from coordinate frame. Parameters ---------- coordinate : ~`astropy.coordinates.BaseFrame` The coordinate of the observer, must be transformable to Heliographic Stonyhurst. rsun : `astropy.units.Quantity` The radius of the Sun. Returns ------- `dict` Containing the WCS meta information * hgln_obs, hglt_obs * dsun_obs * rsun_obs * rsun_ref """ observer = observer.transform_to( frames.HeliographicStonyhurst(obstime=observer.obstime)) coord_meta = {} coord_meta['hgln_obs'] = observer.lon.to_value(u.deg) coord_meta['hglt_obs'] = observer.lat.to_value(u.deg) coord_meta['dsun_obs'] = observer.radius.to_value(u.m) if rsun is not None: coord_meta['rsun_ref'] = rsun.to_value(u.m) coord_meta['rsun_obs'] = sun._angular_radius( rsun, observer.radius).to_value(u.arcsec) return coord_meta
def get_observer_meta(observer, rsun: (u.Mm, None) = None): """ Function to get observer meta from coordinate frame. Parameters ---------- coordinate : `~astropy.coordinates.BaseCoordinateFrame` The coordinate of the observer, must be transformable to Heliographic Stonyhurst. rsun : `astropy.units.Quantity`, optional The radius of the Sun. If ``None``, the RSUN_OBS and RSUN_REF keys are not set. Returns ------- coord_meta : `dict` WCS metadata, with the keys ``['hgln_obs', 'hglt_obs', 'dsun_obs']``, and additionally if ``rsun`` is given ``['rsun_obs', 'rsun_ref']``. """ observer = observer.transform_to( frames.HeliographicStonyhurst(obstime=observer.obstime)) coord_meta = {} coord_meta['hgln_obs'] = observer.lon.to_value(u.deg) coord_meta['hglt_obs'] = observer.lat.to_value(u.deg) coord_meta['dsun_obs'] = observer.radius.to_value(u.m) if rsun is not None: coord_meta['rsun_ref'] = rsun.to_value(u.m) coord_meta['rsun_obs'] = sun._angular_radius( rsun, observer.radius).to_value(u.arcsec) return coord_meta
def test_rsun_missing(euvi_map): """Tests output if 'rsun' is missing""" euvi_no_rsun = euvi_map._new_instance(euvi_map.data, copy.deepcopy(euvi_map.meta)) euvi_no_rsun.meta.pop('rsun', None) r = euvi_no_rsun.observer_coordinate.radius assert euvi_no_rsun.rsun_obs == sun._angular_radius(constants.radius, r)
def test_rsun_missing(): """Tests output if 'rsun' is missing""" euvi_no_rsun = Map(fitspath) euvi_no_rsun.meta['rsun'] = None r = euvi_no_rsun.observer_coordinate.radius with pytest.warns(SunpyUserWarning, match='Missing metadata for solar angular radius'): assert euvi_no_rsun.rsun_obs == sun._angular_radius(constants.radius, r)
def test_hgc_header(hgc_header, hgc_coord): assert isinstance(hgc_header, MetaDict) assert hgc_header['naxis1'] == 10 assert hgc_header['naxis2'] == 20 assert hgc_header['crval1'] == 70 assert hgc_header['crpix1'] == 5.5 assert hgc_header['ctype1'] == "CRLN-CAR" assert hgc_header['crval2'] == -30 assert hgc_header['crpix2'] == 10.5 assert hgc_header['ctype2'] == "CRLT-CAR" assert hgc_header['cunit1'] == "deg" assert hgc_header['cunit2'] == "deg" assert hgc_header[ 'lonpole'] == 180. # for negative reference latitude in a CAR projection assert u.allclose(hgc_header['rsun_ref'] * u.m, hgc_coord.rsun) # Check for observer info for HGC (which is set to "self") assert u.allclose(hgc_header['crln_obs'] * u.deg, hgc_coord.lon) assert u.allclose(hgc_header['crlt_obs'] * u.deg, hgc_coord.lat) assert u.allclose(hgc_header['dsun_obs'] * u.m, hgc_coord.radius) assert u.allclose(hgc_header['rsun_obs'] * u.arcsec, sun._angular_radius(hgc_coord.rsun, hgc_coord.radius)) assert isinstance(WCS(hgc_header), WCS)
def test_carrington_self_observer(): coord = SkyCoord(70 * u.deg, -30 * u.deg, 1 * u.au, observer='self', obstime='2013-10-28 00:00', frame=frames.HeliographicCarrington) header = sunpy.map.make_fitswcs_header(np.zeros((10, 10)), coord) assert header['rsun_obs'] == sun._angular_radius( coord.rsun, coord.radius).to_value(u.arcsec) assert header['dsun_obs'] == coord.radius.to_value(u.m)
def angular_radius(self): """ Angular radius of the Sun as seen by the observer. The ``rsun`` frame attribute is the radius of the Sun in length units. The tangent vector from the observer to the edge of the Sun forms a right-angle triangle with the radius of the Sun as the far side and the Sun-observer distance as the hypotenuse. Thus, the sine of the angular radius of the Sun is ratio of these two distances. """ from sunpy.coordinates.sun import _angular_radius # avoiding a circular import if not isinstance(self.observer, HeliographicStonyhurst): if self.observer is None: raise ValueError("The observer must be defined, not `None`.") raise ValueError("The observer must be fully defined by specifying `obstime`.") return _angular_radius(self.rsun, self.observer.radius)
def solar_angular_radius(coordinates): """ Calculates the solar angular radius as seen by the observer. The tangent vector from the observer to the edge of the Sun forms a right-angle triangle with the radius of the Sun as the far side and the Sun-observer distance as the hypotenuse. Thus, the sine of the angular radius of the Sun is ratio of these two distances. Parameters ---------- coordinates : `~astropy.coordinates.SkyCoord`, `~sunpy.coordinates.frames.Helioprojective` The input coordinate. The coordinate frame must be `~sunpy.coordinates.Helioprojective`. Returns ------- angle : `~astropy.units.Quantity` The solar angular radius. """ return sun._angular_radius(coordinates.rsun, coordinates.observer.radius)
def test_hpc_header(hpc_header, hpc_coord): assert isinstance(hpc_header, MetaDict) assert hpc_header['naxis1'] == 10 assert hpc_header['naxis2'] == 20 assert hpc_header['crval1'] == 0 assert hpc_header['crpix1'] == 5.5 assert hpc_header['ctype1'] == 'HPLN-TAN' assert hpc_header['crval2'] == 100. assert hpc_header['crpix2'] == 10.5 assert hpc_header['ctype2'] == 'HPLT-TAN' assert hpc_header['lonpole'] == 180. assert u.allclose(hpc_header['rsun_ref'] * u.m, hpc_coord.rsun) # Check for observer info for HPC assert u.allclose(hpc_header['hgln_obs'] * u.deg, hpc_coord.observer.lon) assert u.allclose(hpc_header['hglt_obs'] * u.deg, hpc_coord.observer.lat) assert u.allclose(hpc_header['dsun_obs'] * u.m, hpc_coord.observer.radius) assert u.allclose( hpc_header['rsun_obs'] * u.arcsec, sun._angular_radius(hpc_coord.rsun, hpc_coord.observer.radius)) assert isinstance(WCS(hpc_header), WCS)
def make_fitswcs_header(data, coordinate, reference_pixel: u.pix = None, scale: u.arcsec / u.pix = None, rotation_angle: u.deg = None, rotation_matrix=None, instrument=None, telescope=None, observatory=None, wavelength: u.angstrom = None, exposure: u.s = None, projection_code="TAN"): """ Function to create a FITS-WCS header from a coordinate object (`~astropy.coordinates.SkyCoord`) that is required to create a `~sunpy.map.GenericMap`. Parameters ---------- data : `~numpy.ndarray` or `tuple` Array data of Map for which a header is required, or the shape of the data array (in numpy order, i.e. ``(y_size, x_size)``). coordinate : `~astropy.coordinates.SkyCoord` or `~astropy.coordinates.BaseCoordinateFrame` The coordinate of the reference pixel. reference_pixel :`~astropy.units.Quantity` of size 2, optional Reference pixel along each axis. These are expected to be Cartestian ordered, i.e the first index is the x axis, second index is the y axis. Defaults to the center of data array, ``(data.shape[1] - 1)/2., (data.shape[0] - 1)/2.)``, this argument is zero indexed (Python convention) not 1 indexed (FITS convention). scale : `~astropy.units.Quantity` of size 2, optional Pixel scaling along x and y axis (i.e. the spatial scale of the pixels (dx, dy)). These are expected to be Cartestian ordered, i.e [dx, dy]. Defaults to ``([1., 1.] arcsec/pixel)``. rotation_angle : `~astropy.units.Quantity`, optional Coordinate system rotation angle, will be converted to a rotation matrix and stored in the ``PCi_j`` matrix. Can not be specified with ``rotation_matrix``. rotation_matrix : `~numpy.ndarray` of dimensions 2x2, optional Matrix describing the rotation required to align solar North with the top of the image in FITS ``PCi_j`` convention. Can not be specified with ``rotation_angle``. instrument : `~str`, optional Name of the instrument of the observation. telescope : `~str`, optional Name of the telescope of the observation. observatory : `~str`, optional Name of the observatory of the observation. wavelength : `~astropy.units.Quantity`, optional Wavelength of the observation as an astropy quanitity, e.g. 171*u.angstrom. From this keyword, the meta keywords ``wavelnth`` and ``waveunit`` will be populated. exposure : `~astropy.units.Quantity`, optional Exposure time of the observation projection_code : `str`, optional The FITS standard projection code for the new header. Returns ------- `~sunpy.util.MetaDict` The header information required for making a `sunpy.map.GenericMap`. Notes ----- The observer coordinate is taken from the observer property of the ``reference_pixel`` argument. Examples -------- >>> import sunpy.map >>> from sunpy.coordinates import frames >>> from astropy.coordinates import SkyCoord >>> import astropy.units as u >>> import numpy as np >>> data = np.random.rand(1024, 1024) >>> my_coord = SkyCoord(0*u.arcsec, 0*u.arcsec, obstime="2017-08-01", ... observer = 'earth', frame=frames.Helioprojective) >>> my_header = sunpy.map.make_fitswcs_header(data, my_coord) >>> my_map = sunpy.map.Map(data, my_header) """ if not isinstance(coordinate, (SkyCoord, frames.BaseCoordinateFrame)): raise ValueError( "coordinate needs to be a coordinate frame or an SkyCoord instance." ) if isinstance(coordinate, SkyCoord): coordinate = coordinate.frame if coordinate.obstime is None: raise ValueError( "The coordinate needs an observation time, `obstime`.") if isinstance(coordinate, frames.Heliocentric): raise ValueError( "This function does not currently support heliocentric coordinates." ) if hasattr(data, "shape"): shape = data.shape else: shape = data meta_wcs = _get_wcs_meta(coordinate, projection_code) meta_instrument = _get_instrument_meta(instrument, telescope, observatory, wavelength, exposure) meta_wcs.update(meta_instrument) if reference_pixel is None: reference_pixel = u.Quantity([(shape[1] - 1) / 2. * u.pixel, (shape[0] - 1) / 2. * u.pixel]) if scale is None: scale = [1., 1.] * (u.arcsec / u.pixel) meta_wcs['crval1'], meta_wcs['crval2'] = ( coordinate.spherical.lon.to_value(meta_wcs['cunit1']), coordinate.spherical.lat.to_value(meta_wcs['cunit2'])) # Add 1 to go from input 0-based indexing to FITS 1-based indexing meta_wcs['crpix1'], meta_wcs['crpix2'] = ( reference_pixel[0].to_value(u.pixel) + 1, reference_pixel[1].to_value(u.pixel) + 1) meta_wcs['cdelt1'], meta_wcs['cdelt2'] = (scale[0].to_value( meta_wcs['cunit1'] / u.pixel), scale[1].to_value(meta_wcs['cunit2'] / u.pixel)) if rotation_angle is not None and rotation_matrix is not None: raise ValueError( "Can not specify both rotation angle and rotation matrix.") if rotation_angle is not None: lam = meta_wcs['cdelt1'] / meta_wcs['cdelt2'] p = np.deg2rad(rotation_angle) rotation_matrix = np.array([[np.cos(p), -1 * lam * np.sin(p)], [1 / lam * np.sin(p), np.cos(p)]]) if rotation_matrix is not None: (meta_wcs['PC1_1'], meta_wcs['PC1_2'], meta_wcs['PC2_1'], meta_wcs['PC2_2']) = (rotation_matrix[0, 0], rotation_matrix[0, 1], rotation_matrix[1, 0], rotation_matrix[1, 1]) if getattr(coordinate, 'observer', None) is not None: # Have to check for str, as doing == on a SkyCoord and str raises an error if isinstance(coordinate.observer, str) and coordinate.observer == 'self': dsun_obs = coordinate.radius else: dsun_obs = coordinate.observer.radius meta_wcs['rsun_obs'] = sun._angular_radius(coordinate.rsun, dsun_obs).to_value(u.arcsec) meta_dict = MetaDict(meta_wcs) return meta_dict
def test_rsun_missing(): """Tests output if 'rsun' is missing""" euvi_no_rsun = Map(fitspath) euvi_no_rsun.meta['rsun'] = None r = euvi_no_rsun.observer_coordinate.radius assert euvi_no_rsun.rsun_obs == sun._angular_radius(constants.radius, r)
def test_make_fits_header(map_data, hpc_test_header, hgc_test_header, hgs_test_header, hcc_test_header, hpc_test_header_notime): # Check that different coordinate frames return header MetaDict or not in the case of HCC assert isinstance(sunpy.map.make_fitswcs_header(map_data, hpc_test_header), MetaDict) assert isinstance(sunpy.map.make_fitswcs_header(map_data, hgc_test_header), MetaDict) assert isinstance(sunpy.map.make_fitswcs_header(map_data, hgs_test_header), MetaDict) # Raise the HCC error with pytest.raises(ValueError): sunpy.map.make_fitswcs_header(map_data, hcc_test_header) # Check for when coordinate argument isn't given as an `astropy.coordinate.SkyCoord` with pytest.raises(ValueError): sunpy.map.make_fitswcs_header(map_data, map_data) # Check for when an observation time isn't given with pytest.raises(ValueError): sunpy.map.make_fitswcs_header(map_data, hpc_test_header_notime) # Check that correct information is in header MetaDict including observer for HPC header = sunpy.map.make_fitswcs_header(map_data, hpc_test_header) assert header['crval1'] == 0 assert header['crpix1'] == 5.5 assert header['ctype1'] == 'HPLN-TAN' assert u.allclose(header['dsun_obs'], hpc_test_header.frame.observer.radius.to_value(u.m)) assert u.allclose(header['rsun_ref'] * u.m, hpc_test_header.frame.rsun) assert u.allclose( header['rsun_obs'] * u.arcsec, sun._angular_radius(header['rsun_ref'] * u.m, header['dsun_obs'] * u.m)) assert isinstance(WCS(header), WCS) # Check no observer info for HGS header = sunpy.map.make_fitswcs_header(map_data, hgs_test_header) assert 'dsun_obs' not in header assert 'rsun_obs' not in header assert isinstance(WCS(header), WCS) # Check for observer info for HGC header = sunpy.map.make_fitswcs_header(map_data, hgc_test_header) assert u.allclose(header['dsun_obs'], hgc_test_header.frame.observer.radius.to_value(u.m)) assert 'rsun_obs' not in header assert isinstance(WCS(header), WCS) # Check arguments not given as astropy Quantities with pytest.raises(TypeError): header = sunpy.map.make_fitswcs_header(map_data, hpc_test_header, reference_pixel=[0, 0]) header = sunpy.map.make_fitswcs_header(map_data, hpc_test_header, scale=[0, 0]) # Check arguments of reference_pixel and scale have to be given in astropy units of pix, and arcsec/pix with pytest.raises(u.UnitsError): header = sunpy.map.make_fitswcs_header(map_data, hpc_test_header, reference_pixel=u.Quantity( [0, 0])) header = sunpy.map.make_fitswcs_header(map_data, hpc_test_header, scale=u.Quantity([0, 0])) header = sunpy.map.make_fitswcs_header(map_data, hpc_test_header, scale=u.Quantity([0, 0] * u.arcsec)) # Check keyword helper arguments header = sunpy.map.make_fitswcs_header(map_data, hpc_test_header, instrument='test name') assert header['instrume'] == 'test name' # Check returned MetaDict will make a `sunpy.map.Map` map_test = sunpy.map.Map(map_data, header) assert isinstance(map_test, sunpy.map.mapbase.GenericMap)
def make_fitswcs_header(data, coordinate, reference_pixel: u.pix = None, scale: u.arcsec / u.pix = None, rotation_angle: u.deg = None, rotation_matrix=None, instrument=None, telescope=None, observatory=None, wavelength: u.angstrom = None, exposure: u.s = None, projection_code="TAN"): """ Function to create a FITS-WCS header from a coordinate object (`~astropy.coordinates.SkyCoord`) that is required to create a `~sunpy.map.GenericMap`. Parameters ---------- data : `~numpy.ndarray` or `tuple` Array data of Map for which a header is required, or the shape of the data array (in numpy order, i.e. ``(y_size, x_size)``). coordinate : `~astropy.coordinates.SkyCoord` or `~astropy.coordinates.BaseCoordinateFrame` The coordinate of the reference pixel. reference_pixel :`~astropy.units.Quantity` of size 2, optional Reference pixel along each axis. These are expected to be Cartestian ordered, i.e the first index is the x axis, second index is the y axis. Defaults to the center of data array, ``(data.shape[1] - 1)/2., (data.shape[0] - 1)/2.)``, this argument is zero indexed (Python convention) not 1 indexed (FITS convention). scale : `~astropy.units.Quantity` of size 2, optional Pixel scaling along x and y axis (i.e. the spatial scale of the pixels (dx, dy)). These are expected to be Cartestian ordered, i.e [dx, dy]. Defaults to ``([1., 1.] arcsec/pixel)``. rotation_angle : `~astropy.units.Quantity`, optional Coordinate system rotation angle, will be converted to a rotation matrix and stored in the ``PCi_j`` matrix. Can not be specified with ``rotation_matrix``. rotation_matrix : `~numpy.ndarray` of dimensions 2x2, optional Matrix describing the rotation required to align solar North with the top of the image in FITS ``PCi_j`` convention. Can not be specified with ``rotation_angle``. instrument : `~str`, optional Name of the instrument of the observation. telescope : `~str`, optional Name of the telescope of the observation. observatory : `~str`, optional Name of the observatory of the observation. wavelength : `~astropy.units.Quantity`, optional Wavelength of the observation as an astropy quanitity, e.g. 171*u.angstrom. From this keyword, the meta keywords ``wavelnth`` and ``waveunit`` will be populated. exposure : `~astropy.units.Quantity`, optional Exposure time of the observation projection_code : `str`, optional The FITS standard projection code for the new header. Returns ------- `~sunpy.util.MetaDict` The header information required for making a `sunpy.map.GenericMap`. Notes ----- The observer coordinate is taken from the observer property of the ``reference_pixel`` argument. Examples -------- >>> import sunpy.map >>> from sunpy.coordinates import frames >>> from astropy.coordinates import SkyCoord >>> import astropy.units as u >>> import numpy as np >>> data = np.random.rand(1024, 1024) >>> my_coord = SkyCoord(0*u.arcsec, 0*u.arcsec, obstime="2017-08-01", ... observer = 'earth', frame=frames.Helioprojective) >>> my_header = sunpy.map.make_fitswcs_header(data, my_coord) >>> my_map = sunpy.map.Map(data, my_header) """ coordinate = _validate_coordinate(coordinate) if hasattr(data, "shape"): shape = data.shape else: shape = data meta_wcs = _get_wcs_meta(coordinate, projection_code) meta_wcs = _set_instrument_meta(meta_wcs, instrument, telescope, observatory, wavelength, exposure) meta_wcs = _set_transform_params(meta_wcs, coordinate, reference_pixel, scale, shape) meta_wcs = _set_rotation_params(meta_wcs, rotation_angle, rotation_matrix) if getattr(coordinate, 'observer', None) is not None: # Have to check for str, as doing == on a SkyCoord and str raises an error if isinstance(coordinate.observer, str) and coordinate.observer == 'self': dsun_obs = coordinate.radius else: dsun_obs = coordinate.observer.radius meta_wcs['rsun_obs'] = sun._angular_radius(coordinate.rsun, dsun_obs).to_value(u.arcsec) meta_dict = MetaDict(meta_wcs) return meta_dict
def draw_limb(axes, observer, *, rsun: u.m = R_sun, resolution=1000, **kwargs): """ Draws the solar limb as seen by the specified observer. The limb is a circle for only the simplest plots. If the specified observer of the limb is different from the observer of the coordinate frame of the plot axes, not only may the limb not be a true circle, a portion of the limb may be hidden from the observer. In that case, the circle is divided into visible and hidden segments, represented by solid and dotted lines, respectively. Parameters ---------- axes : `~matplotlib.axes` or ``None`` Axes to plot limb on. observer : `astropy.coordinates.SkyCoord` Observer coordinate for which the limb is drawn. rsun : `~astropy.units.Quantity` Solar radius (in physical length units) at which to draw the limb. Defaults to the standard photospheric radius. resolution : `int` The number of points to use to represent the limb. Returns ------- visible : `~matplotlib.patches.Polygon` or `~matplotlib.patches.Circle` or None The patch added to the axes for the visible part of the limb (i.e., the "near" side of the Sun). hidden : `~matplotlib.patches.Polygon` or None The patch added to the axes for the hidden part of the limb (i.e., the "far" side of the Sun). Notes ----- Keyword arguments are passed onto the patches. If the limb is a true circle, ``visible`` will instead be `~matplotlib.patches.Circle` and ``hidden`` will be ``None``. If there are no hidden points (e.g., on a synoptic map any limb is fully visible) ``hidden`` will be ``None``. If there are no visible points (e.g., for an observer on the opposite side of the Sun to the map observer) ``visible`` will be ``None``. To avoid triggering Matplotlib auto-scaling, these patches are added as artists instead of patches. One consequence is that the plot legend is not populated automatically when the limb is specified with a text label. See :ref:`sphx_glr_gallery_text_labels_and_annotations_custom_legends.py` in the Matplotlib documentation for examples of creating a custom legend. """ if not wcsaxes_compat.is_wcsaxes(axes): raise ValueError('axes must be a WCSAxes') c_kw = {'fill': False, 'color': 'white', 'zorder': 100} c_kw.update(kwargs) transform = axes.get_transform('world') # transform is always passed on as a keyword argument c_kw.setdefault('transform', transform) # If the observer matches the axes's observer frame and is Helioprojective, use a Circle axes_frame = axes._transform_pixel2world.frame_out if isinstance(axes_frame, Helioprojective): axes_observer = SkyCoord(axes_frame.observer) if axes_observer.separation_3d(observer) < 1 * u.m: distance = observer.transform_to( HeliocentricInertial).spherical.distance # Obtain the solar radius and the world->pixel transform angular_radius = _angular_radius(rsun, distance) circ = patches.Circle([0, 0], radius=angular_radius.to_value(u.deg), **c_kw) axes.add_artist(circ) return circ, None # Otherwise, we use Polygon to be able to distort the limb # Create the limb coordinate array using Heliocentric Radial limb = get_limb_coordinates(observer, rsun, resolution) # Transform the limb to the axes frame and get the 2D vertices visible, hidden = _plot_vertices(limb, axes, axes_frame, rsun, **kwargs) return visible, hidden
def draw_limb(axes, observer, *, rsun: u.m = R_sun, resolution=1000, **kwargs): """ Draws the solar limb as seen by the specified observer. The limb is a circle for only the simplest plots. If the specified observer of the limb is different from the observer of the coordinate frame of the plot axes, not only may the limb not be a true circle, a portion of the limb may be hidden from the observer. In that case, the circle is divided into visible and hidden segments, represented by solid and dotted lines, respectively. Parameters ---------- axes : `~matplotlib.axes` or ``None`` Axes to plot limb on. observer : `astropy.coordinates.SkyCoord` Observer coordinate for which the limb is drawn. rsun : `~astropy.units.Quantity` Solar radius (in physical length units) at which to draw the limb. Defaults to the standard photospheric radius. resolution : `int` The number of points to use to represent the limb. Returns ------- visible : `~matplotlib.patches.Polygon` or `~matplotlib.patches.Circle` or None The patch added to the axes for the visible part of the limb (i.e., the "near" side of the Sun). hidden : `~matplotlib.patches.Polygon` or None The patch added to the axes for the hidden part of the limb (i.e., the "far" side of the Sun). Notes ----- Keyword arguments are passed onto the patches. If the limb is a true circle, ``visible`` will instead be `~matplotlib.patches.Circle` and ``hidden`` will be ``None``. If there are no hidden points (e.g., on a synoptic map any limb is fully visible) ``hidden`` will be ``None``. If there are no visible points (e.g., for an observer on the opposite side of the Sun to the map observer) ``visible`` will be ``None``. To avoid triggering Matplotlib auto-scaling, these patches are added as artists instead of patches. One consequence is that the plot legend is not populated automatically when the limb is specified with a text label. See :ref:`sphx_glr_gallery_text_labels_and_annotations_custom_legends.py` in the Matplotlib documentation for examples of creating a custom legend. """ if not wcsaxes_compat.is_wcsaxes(axes): raise ValueError('axes must be a WCSAxes') c_kw = {'fill': False, 'color': 'white', 'zorder': 100} c_kw.update(kwargs) transform = axes.get_transform('world') # transform is always passed on as a keyword argument c_kw.setdefault('transform', transform) # If the observer matches the axes's observer frame and is Helioprojective, use a Circle axes_frame = axes._transform_pixel2world.frame_out if isinstance(axes_frame, Helioprojective): axes_observer = SkyCoord(axes_frame.observer) if axes_observer.separation_3d(observer) < 1 * u.m: distance = observer.transform_to( HeliocentricInertial).spherical.distance # Obtain the solar radius and the world->pixel transform angular_radius = _angular_radius(rsun, distance) circ = patches.Circle([0, 0], radius=angular_radius.to_value(u.deg), **c_kw) axes.add_artist(circ) return circ, None # Otherwise, we use Polygon to be able to distort the limb # Create the limb coordinate array using Heliocentric Radial limb = get_limb_coordinates(observer, rsun, resolution) # Transform the limb to the axes frame and get the 2D vertices limb_in_axes = limb.transform_to(axes_frame) Tx = limb_in_axes.spherical.lon.to_value(u.deg) Ty = limb_in_axes.spherical.lat.to_value(u.deg) vertices = np.array([Tx, Ty]).T # Determine which points are visible if hasattr(axes_frame, 'observer'): # The reference distance is the distance to the limb for the axes # observer rsun = getattr(axes_frame, 'rsun', rsun) reference_distance = np.sqrt(axes_frame.observer.radius**2 - rsun**2) is_visible = limb_in_axes.spherical.distance <= reference_distance else: # If the axes has no observer, the entire limb is considered visible is_visible = np.ones_like(limb_in_axes.spherical.distance, bool, subok=False) # Identify discontinuities in the limb. Uses the same approach as # astropy.visualization.wcsaxes.grid_paths.get_lon_lat_path() step = np.sqrt((vertices[1:, 0] - vertices[:-1, 0])**2 + (vertices[1:, 1] - vertices[:-1, 1])**2) continuous = np.concatenate([[True, True], step[1:] < 100 * step[:-1]]) visible, hidden = None, None if np.sum(is_visible) > 0: # Create the Polygon for the near side of the Sun (using a solid line) if 'linestyle' not in kwargs: c_kw['linestyle'] = '-' visible = patches.Polygon(vertices, **c_kw) _modify_polygon_visibility(visible, is_visible & continuous) # Add patches as artists rather than patches to avoid triggering auto-scaling axes.add_artist(visible) if np.sum(~is_visible) > 0: # Create the Polygon for the far side of the Sun (using a dotted line) if 'linestyle' not in kwargs: c_kw['linestyle'] = ':' hidden = patches.Polygon(vertices, **c_kw) _modify_polygon_visibility(hidden, ~is_visible & continuous) axes.add_artist(hidden) return visible, hidden