Source code for uranography.armillary

"""Interactive sky map that works like an armillary sphere.
"""

import bokeh
import numpy as np
import panel as pn
from IPython.display import display

from .sphere import rotate_cart
from .spheremap import MovingSphereMap


[docs] class ArmillarySphere(MovingSphereMap): """Orthographic projection of the sky, presented like an armillary sphere. Parameters ---------- plot : `bokeh.plotting.figure`, optional Figure to which to add the map, by default None mjd : `float`, optional The Modified Julian Date location: `EarthLocation` or `str`, optional The location of the observatory, defaults to lsst. """ x_col = "x_orth" y_col = "y_orth" update_js_fnames = ("coord_utils.js", "orthographic.js") update_js_command = "updateOrthoData()" transform_js_fnames = ("coord_utils.js", "orthographic.js") transform_js_call = "return orthoTransform()" proj_slider_keys = ["alt", "az", "mjd"] default_title = "Armillary Sphere"
[docs] def to_orth_zenith(self, hpx, hpy, hpz): """Convert healpy vector coordinates to orthographic coordinates Parameters ---------- hpx : `numpy.ndarray` Healpy vector x coordinates x=1, y=0, z=0 corresponds to R.A.=0 deg, Decl=0 deg. x=-1, y=0, z=0 corresponds to R.A.=180 deg, Decl=0 deg. hpy : `numpy.ndarray` Healpy vector y coordinates x=0, y=1, z=0 corresponds to R.A.=90 deg, Decl=0 deg. x=0, y=-1, z=0 corresponds to R.A.=270 deg, Decl=0 deg. hpz : `numpy.ndarray` Healpy vector z coordinates x=0, y=0, z=1 corresponds to Decl=90 deg. x=0, y=0, z=-1 corresponds to Decl=-90 deg. Returns ------- x : `numpy.ndarray` Orthographic x coordinate (positive to the right) y : `numpy.ndarray` Orthographic y coordinate (positive up) z : `numpy.ndarray` Orthographic z coordinate (positive toward the viewer) """ x1, y1, z1 = rotate_cart(0, 0, 1, -90, hpx, hpy, hpz) # pylint: disable=C0103 x2, y2, z2 = rotate_cart(1, 0, 0, self.location.lat.deg + 90, x1, y1, z1) # pylint: disable=C0103 npole_x1, npole_y1, npole_z1 = rotate_cart(0, 0, 1, -90, 0, 0, 1) npole_x2, npole_y2, npole_z2 = rotate_cart( 1, 0, 0, self.location.lat.deg + 90, npole_x1, npole_y1, npole_z1 ) x3, y3, z3 = rotate_cart(npole_x2, npole_y2, npole_z2, -self.lst, x2, y2, z2) # pylint: disable=C0103 # x3, y3, z3 have the center right, now rotate it so that north is "up" # the 2-3 transform is about the axis through the n pole, so # the n pole is the same in 3 an 2. # Find the direction of the north pole, angle form +x axis toward # +y axis npole_x3, npole_y3 = npole_x2, npole_y2 orient = np.degrees(np.arctan2(npole_y3, npole_x3)) # To the n pole on the y axis, we must rotate it the rest of the 90 deg x4, y4, z4 = rotate_cart(0, 0, 1, 90 - orient, x3, y3, z3) # pylint: disable=C0103 # In astronomy, we are looking out of the sphere from the center to the # back (which naturally results in west to the right). # Positive z is out of the screen behind us, and we are at the center, # so to visible part is when z is negative (coords[2]<=0). # So, set the points with positive z to NaN so they are # not shown, because they are behind the observer. # Use np.finfo(z3[0]).resolution instead of exactly 0, because the # assorted trig operations result in values slightly above or below # 0 when the horizon is in principle exactly 0, and this gives an # irregularly dotted/dashed appearance to the horizon if # a cutoff of exactly 0 is used. try: orth_invisible = z4 > np.finfo(z4.dtype).resolution # pylint: disable=E1101 except ValueError: # If z4 is somehow an integer orth_invisible = z4 > 0 x4[orth_invisible] = np.nan y4[orth_invisible] = np.nan z4[orth_invisible] = np.nan return x4, y4, z4
def _add_projection_columns(self, hpix, nside=None, projector=None): """Adds pre-calculated projection columns for this projection.""" hp_vec_cols = ["x_hp", "y_hp", "z_hp"] corners = hpix.to_df().loc[:, ["hpid"] + hp_vec_cols].explode(column=hp_vec_cols, ignore_index=True) corners["x_orth"], corners["y_orth"], corners["z_orth"] = self.to_orth_zenith( corners["x_hp"], corners["y_hp"], corners["z_hp"] ) hpix_data = corners.groupby("hpid").agg(lambda x: x.tolist()) for column in ["x_orth", "y_orth", "z_orth"]: hpix.add(hpix_data[column].tolist(), name=column) return hpix
[docs] def set_js_update_func(self, data_source): """Set the javascript update functions for each slider Parameters ---------- data_source : `bokeh.models.ColumnDataSource` The bokeh data source to update. """ update_func = bokeh.models.CustomJS( args=dict( data_source=data_source, center_alt_slider=self.sliders["alt"], center_az_slider=self.sliders["az"], mjd_slider=self.sliders["mjd"], lat=self.location.lat.deg, lon=self.location.lon.deg, ), code=self.update_js, ) self.update_functions.append(update_func) self.force_update_time.js_on_change("text", update_func) for proj_slider_key in self.proj_slider_keys: try: self.sliders[proj_slider_key].js_on_change("value", update_func) except KeyError: pass
[docs] def add_sliders(self, center_alt=90, center_az=180): """Add (already defined) sliders to the map.""" super().add_sliders() self.sliders["alt"] = bokeh.models.Slider( start=-90, end=90, value=center_alt, step=np.pi / 180, title="center alt", ) self.sliders["az"] = bokeh.models.Slider( start=-90, end=360, value=center_az, step=np.pi / 180, title="center Az" ) self.sliders["mjd"] = bokeh.models.Slider( start=self.mjd - 1, end=self.mjd + 1, value=self.mjd, step=1.0 / (24 * 60), title="MJD", ) self.visible_slider_names.append("alt") self.visible_slider_names.append("az") self.visible_slider_names.append("mjd")
[docs] def notebook_display(self): """Use panel to show the figure in within a notebook.""" template = pn.Template( """ {% extends base %} {% block postamble %} <script src="https://unpkg.com/gpu.js@latest/dist/gpu-browser.min.js"></script> {% endblock %} """ ) template.add_panel("main", self.viewable) display(template) self.update()