#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# @Author: José Sánchez-Gallego (gallegoj@uw.edu)
# @Date: 2017-10-17
# @Filename: plot.py
# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause)
#
# @Last modified by: José Sánchez-Gallego (gallegoj@uw.edu)
# @Last modified time: 2019-03-29 01:23:34
import matplotlib.patches
import matplotlib.pyplot as plt
import matplotlib.transforms
import numpy
from lvmsurveysim.target import _VALID_FRAMES
__all__ = ['get_axes', 'transform_patch_mollweide', 'convert_to_mollweide', 'plot_ellipse']
__MOLLWEIDE_ORIGIN__ = 180
[docs]def get_axes(projection='rectangular', frame='icrs', ylim=None):
"""Returns axes for a particular projection.
Parameters
----------
projection : str
The type of projection of the axes returned. Either ``'rectangular'``
for a normal, cartesian, projection, or
`mollweide <https://en.wikipedia.org/wiki/Mollweide_projection>`_.
frame : str
The reference frame of the axes. Must be one of
`~lvmsurveysim.target._VALID_FRAMES`. Used to define
the axis labels.
ylim : tuple
The range to be used to limit the y-axis. Only relevant if
``projection='rectangular'``. If `None`, ``(-90, 90)`` will be used.
Returns
-------
figax
The new matplotlib `~matplotlib.figure.Figure` and
`~matplotlig.axes.Axes` objects for the selected projection.
"""
assert frame in _VALID_FRAMES, 'invalid frame'
if projection == 'rectangular':
fig = plt.figure(figsize=(10, 6))
ax = fig.add_subplot(111)
ax.set_xlim(360, 0)
if ylim:
ax.set_ylim(**ylim)
else:
ax.set_ylim(-90, 90)
ax.grid(True)
elif projection == 'mollweide':
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(111, projection='mollweide')
org = __MOLLWEIDE_ORIGIN__
tick_labels = numpy.array([150., 120, 90, 60, 30, 0,
330, 300, 270, 240, 210])
tick_labels = numpy.remainder(tick_labels + 360 + org, 360)
tick_labels = numpy.array(tick_labels / 15., int)
tickStr = []
for tick_label in tick_labels:
#tickStr.append('')
tickStr.append('${0:d}^h$'.format(tick_label))
# Bug fix: if we have an even number of ticklabels, starting at 1 and skipping ever other will produce a mismatch in the number of tics and lables.
# Temporary fix, try to set them, but don't break if the mismatch exists.
try:
ax.set_xticklabels(tickStr) # we add the scale on the x axis
except:
pass
ax.grid(True)
else:
raise ValueError('invalid projection')
if frame == 'icrs':
ax.set_xlabel(r'$\alpha_{2000}\,{\rm [deg]}$')
ax.set_ylabel(r'$\delta_{2000}\,{\rm [deg]}$')
elif frame == 'galactic':
ax.set_xlabel(r'$\rm l\,[deg]$')
ax.set_ylabel(r'$\rm b\,[deg]$')
return fig, ax
[docs]def convert_to_mollweide(ra, dec):
"""Converts ``[0, 360)`` coordinates to Mollweide-valid values.
Converts values to radians and offsets the Longitude to match the custom
Mollweide projection used here. Flips RA so that E is left.
Parameters
----------
ra : ~numpy.ndarray
ra in degrees of coordinates to be converted
dec : ~numpy.ndarray
dec in degrees of coordinates to be converted
Returns
-------
ra0,dec0 in radians suitable to plot in mollwede projection
"""
ra = numpy.asarray([ra]) if numpy.isscalar(ra) else numpy.asarray(ra)
dec = numpy.asarray([dec]) if numpy.isscalar(dec) else numpy.asarray(dec)
ra0 = numpy.remainder(ra + 360 - __MOLLWEIDE_ORIGIN__, 360) # shift RA values
ra0[ra0 > 180.] -= 360 # convert range to [-180,180]
return -numpy.deg2rad(ra0),numpy.deg2rad(dec) # reverse RA so that East is left
def transform_vertices_mollweide(vertices):
"""Applies a transformation to a set of vertices for the Mollweide projection.
The Mollweide projection assumes the plotted values are in radians. In
addition, the axes returned by `.get_axes` for a Mollweide projection have
the tick labels modified to place the centre at a different position from
the default 0 rad. RA is flipped, so that East is left.
See also `.transform_to_mollweide`.
Note that the Mollweide projection doesn't provide wrapping. Large regions
that cross the edge of the projection will not be displayed completely.
Parameters:
vertices : `~numpy.array`
The Nx2 array of vertices to be transformed.
Returns:
vertices : `~numpy.array`
The Nx2 array of vertices in Mollweide coordinates.
"""
v = vertices.T
r, d = convert_to_mollweide(v[0,:], v[1,:])
return numpy.array([r,d]).T
[docs]def plot_ellipse(ax, ra, dec, width=3.0, height=None, origin=0,
bgcolor='b', zorder=0, alpha=0.8):
"""Plots an ellipse path of a given angular size."""
ra = numpy.atleast_1d(ra)
dec = numpy.atleast_1d(dec)
height = width or height
ra = numpy.remainder(ra + 360 - origin, 360) # shift RA values
ind = ra > 180.
ra[ind] -= 360 # scale conversion to [-180, 180]
ra = -ra # reverse the scale: East to the left
for ii in range(len(ra)):
ell = matplotlib.patches.Ellipse(
xy=(numpy.radians(ra[ii]), numpy.radians(dec[ii])),
width=numpy.radians(width) / numpy.cos(numpy.radians(dec[ii])),
height=numpy.radians(height),
edgecolor='None',
lw=0.0,
facecolor=bgcolor,
zorder=zorder,
alpha=alpha)
ax.add_patch(ell)
return ax