Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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)
Beispiel #7
0
    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)
Beispiel #8
0
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)
Beispiel #9
0
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)
Beispiel #10
0
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
Beispiel #11
0
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)
Beispiel #12
0
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)
Beispiel #13
0
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
Beispiel #14
0
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
Beispiel #15
0
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