def test_hgc_self_observer(): # Test specifying observer='self' for HGC obstime = Time('2001-01-01') hgc = HeliographicCarrington(10*u.deg, 20*u.deg, 3*u.AU, observer='self', obstime=obstime) # Transform to HGS (i.e., observer='self' in the source frame) hgs = hgc.transform_to(HeliographicStonyhurst(obstime=obstime)) # Manually calculate the post-transformation longitude lon = sun.L0(obstime, light_travel_time_correction=False, nearest_point=False, aberration_correction=False) lon += (hgc.radius - _RSUN) / speed_of_light * sidereal_rotation_rate assert_quantity_allclose(Longitude(hgs.lon + lon), hgc.lon) assert_quantity_allclose(hgs.lat, hgc.lat) assert_quantity_allclose(hgs.radius, hgc.radius) # Transform back to HGC (i.e., observer='self' in the destination frame) hgc_loop = hgs.transform_to(hgc.replicate_without_data()) assert_quantity_allclose(hgc_loop.lon, hgc.lon) assert_quantity_allclose(hgc_loop.lat, hgc.lat) assert_quantity_allclose(hgc_loop.radius, hgc.radius)
def test_hgc_hgc(): # Test HGC loopback transformation obstime = Time('2001-01-01') old = SkyCoord(90*u.deg, 10*u.deg, 1*u.AU, frame=HeliographicCarrington(obstime=obstime)) new = old.transform_to(HeliographicCarrington(obstime=obstime + 1*u.day)) assert_quantity_allclose(new.lon, old.lon - 14.1844*u.deg, atol=1e-4*u.deg) # solar rotation assert_quantity_allclose(new.lat, old.lat, atol=1e-4*u.deg) assert_quantity_allclose(new.radius, old.radius, atol=1e-5*u.AU)
def test_hgc_hgc(): # Test HGC loopback transformation obstime = Time('2001-01-01') old = SkyCoord(90*u.deg, 10*u.deg, 1*u.AU, frame=HeliographicCarrington(observer='earth', obstime=obstime)) new = old.transform_to(HeliographicCarrington(observer='earth', obstime=obstime + 1*u.day)) assert_quantity_allclose(new.lon, 75.815607 * u.deg, atol=1e-7*u.deg) # solar rotation # These are not equal to the old values, because the coordinates stay fixed # in inertial space, whilst the frame (fixed to the center of the Sun) # moves slightly. assert_quantity_allclose(new.lat, 9.999963 * u.deg, atol=1e-7*u.deg) assert_quantity_allclose(new.radius, 1.000009 * u.AU, atol=1e-7*u.AU)
def test_hgs_hgc_sunspice(): # Compare our HGS->HGC transformation against SunSPICE # "HEQ" is another name for HEEQ, which is equivalent to Heliographic Stonyhurst # "Carrington" does not include light travel time to the observer, which our HGC includes # # IDL> coord = [1.d, 0.d, 10.d] # IDL> convert_sunspice_lonlat, '2019-06-01', coord, 'HEQ', 'Carrington', /au, /degrees # IDL> print, coord # 1.0000000 16.688242 10.000000 old = SkyCoord(0 * u.deg, 10 * u.deg, 1 * u.AU, frame=HeliographicStonyhurst(obstime='2019-06-01')) new = old.transform_to(HeliographicCarrington(observer='earth')) # Calculate the difference in longitude due to light travel time from the Sun to the Earth delta_lon = sidereal_rotation_rate * (sun.earth_distance(old.obstime) - _RSUN) / speed_of_light assert_quantity_allclose(new.lon, 16.688242 * u.deg + delta_lon, atol=1e-2 * u.arcsec, rtol=0) assert_quantity_allclose(new.lat, old.lat) assert_quantity_allclose(new.radius, old.radius)
def test_transform_with_sun_center(): sun_center = SkyCoord(0 * u.deg, 0 * u.deg, 0 * u.AU, frame=HeliographicStonyhurst(obstime="2001-01-01")) with transform_with_sun_center(): result1 = sun_center.transform_to( HeliographicStonyhurst(obstime="2001-02-01")) # The coordinate should stay pointing at Sun center assert_quantity_allclose(result1.lon, sun_center.lon) assert_quantity_allclose(result1.lat, sun_center.lat) assert_quantity_allclose(result1.radius, sun_center.radius) other = SkyCoord(10 * u.deg, 20 * u.deg, 1 * u.AU, frame=HeliographicStonyhurst(obstime="2001-01-01")) with transform_with_sun_center(): result2 = other.transform_to( HeliographicCarrington(observer='earth', obstime="2001-02-01")) # The coordinate should stay at the same latitude and the same distance from Sun center assert_quantity_allclose(result2.lat, other.lat) assert_quantity_allclose(result2.radius, other.radius)
def test_hgc_loopback_self_observer(): # Test the HGC loopback where only one end has observer='self' obstime = Time('2001-01-01') coord = HeliographicCarrington(10*u.deg, 20*u.deg, 3*u.AU, observer='self', obstime=obstime) new_observer = HeliographicStonyhurst(40*u.deg, 50*u.deg, 6*u.AU) new_frame = HeliographicCarrington(observer=new_observer, obstime=obstime) new_coord = coord.transform_to(new_frame) # Manually calculate the longitude shift due to the difference in Sun-observer distance lon = (6*u.AU - 3*u.AU) / speed_of_light * sidereal_rotation_rate assert_quantity_allclose(new_coord.lon, coord.lon + lon) assert_quantity_allclose(new_coord.lat, coord.lat) assert_quantity_allclose(new_coord.radius, coord.radius)
def test_hgs_cartesian_rep_to_hgc(): # This test checks transformation HGS->HCC when the coordinate is in a Cartesian # representation and that it is the same as a transformation from an HGS frame with a # spherical representation obstime = "2011-01-01" hgscoord_cart = SkyCoord(x=1*u.km, y=0.*u.km, z=0.*u.km, frame=HeliographicStonyhurst(obstime=obstime), representation_type='cartesian') hgscoord_sph = hgscoord_cart.copy() hgscoord_sph.representation_type = 'spherical' # HGC hgccoord_cart = hgscoord_cart.transform_to(HeliographicCarrington(obstime=obstime)) hgccoord_sph = hgscoord_sph.transform_to(HeliographicCarrington(obstime=obstime)) assert_quantity_allclose(hgccoord_cart.lat, hgccoord_sph.lat) assert_quantity_allclose(hgccoord_cart.lon, hgccoord_sph.lon) assert_quantity_allclose(hgccoord_cart.radius, hgccoord_sph.radius)
def test_hgc_hgc_different_observers(): obstime = Time('2001-01-01') hgc_earth = HeliographicCarrington(observer='earth', obstime=obstime) hgc_mars = HeliographicCarrington(observer='mars', obstime=obstime) hgc_sun = HeliographicCarrington(observer='sun', obstime=obstime) sc = SkyCoord(10*u.deg, 20*u.deg, 1*u.AU, frame=HeliographicStonyhurst(obstime=obstime)) sc_hgc_earth = sc.transform_to(hgc_earth) sc_hgc_mars = sc_hgc_earth.transform_to(hgc_mars) sc_hgc_sun = sc_hgc_mars.transform_to(hgc_sun) ltt_earth = hgc_earth.observer.radius / speed_of_light assert_quantity_allclose(sc_hgc_earth.lon - sc_hgc_sun.lon, ltt_earth * sidereal_rotation_rate) ltt_mars = hgc_mars.observer.radius / speed_of_light assert_quantity_allclose(sc_hgc_mars.lon - sc_hgc_sun.lon, ltt_mars * sidereal_rotation_rate)
def test_hgs_hgc_roundtrip(): obstime = "2011-01-01" hgsin = HeliographicStonyhurst(lat=10*u.deg, lon=20*u.deg, obstime=obstime) hgcout = hgsin.transform_to(HeliographicCarrington(obstime=obstime)) assert_quantity_allclose(hgsin.lat, hgcout.lat) assert_quantity_allclose(hgsin.lon + sun.L0(obstime), hgcout.lon) hgsout = hgcout.transform_to(HeliographicStonyhurst(obstime=obstime)) assert_quantity_allclose(hgsout.lat, hgsin.lat) assert_quantity_allclose(hgsout.lon, hgsin.lon)
def test_rsun_preservation(): # Check that rsun is preserved when transforming between any two frames with that attribute args_in = {'obstime': '2001-01-01', 'rsun': 690*u.Mm} args_out = {'obstime': '2001-02-01', 'rsun': 700*u.Mm} coords_in = [Helioprojective(0*u.deg, 0*u.deg, 1*u.AU, observer='earth', **args_in), HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU, **args_in), HeliographicCarrington(0*u.deg, 0*u.deg, 1*u.AU, observer='earth', **args_in)] for coord in coords_in: for frame in coords_in: out_coord = coord.transform_to(frame.replicate(**args_out)) assert_quantity_allclose(out_coord.rsun, args_out['rsun'])
def test_velocity_hgs_hgc(): # Construct a simple HGS coordinate with zero velocity obstime = Time(['2019-01-01', '2019-04-01', '2019-07-01', '2019-10-01']) pos = CartesianRepresentation(1, 0, 0)*u.AU vel = CartesianDifferential(0, 0, 0)*u.km/u.s loc = (pos.with_differentials(vel))._apply('repeat', obstime.size) coord = SkyCoord(HeliographicStonyhurst(loc, obstime=obstime)) # The induced velocity in HGC should be entirely longitudinal, and approximately equal to one # full rotation every mean synodic period (27.2753 days) hgc_frame = HeliographicCarrington(observer='earth', obstime=obstime) new = coord.transform_to(hgc_frame) new_vel = new.data.differentials['s'].represent_as(SphericalDifferential, new.data) assert_quantity_allclose(new_vel.d_lon, -360*u.deg / (27.27253*u.day), rtol=1e-2) assert_quantity_allclose(new_vel.d_lat, 0*u.deg/u.s) assert_quantity_allclose(new_vel.d_distance, 0*u.km/u.s, atol=1e-7*u.km/u.s)
def prime_meridian(axes, *, rsun: u.m = R_sun, resolution=500, **kwargs): """ Draws the solar prime meridian (zero Carrington longitude) as seen by the axes observer. Hidden parts are drawn as a dotted line. Parameters ---------- axes : `matplotlib.axes.Axes` The axes to plot the prime meridian on, or "None" to use current axes. rsun : `~astropy.units.Quantity` Solar radius (in physical length units) at which to draw the solar prime meridian. Defaults to the standard photospheric radius. resolution : `int` The number of points used to represent the prime meridian. Returns ------- visible : `~matplotlib.patches.Polygon` The patch added to the axes for the visible part of the solar equator. hidden : `~matplotlib.patches.Polygon` The patch added to the axes for the hidden part of the solar equator. """ if not wcsaxes_compat.is_wcsaxes(axes): raise ValueError('axes must be a WCSAxes') axes_frame = wcsapi_to_celestial_frame(axes.wcs) if not hasattr(axes_frame, 'observer'): raise ValueError( 'the coordinate frame of the WCSAxes does not have an observer, ' 'so zero Carrington longitude cannot be determined.') observer = axes_frame.observer lon = 0 * u.deg lon0 = SkyCoord(np.ones(resolution) * lon, np.linspace(-90, 90, resolution) * u.deg, radius=rsun, frame=HeliographicCarrington(observer=observer, obstime=axes_frame.obstime)) visible, hidden = _plot_vertices(lon0, axes, axes_frame, rsun, close_path=False, **kwargs) return visible, hidden
def from_pfsspack(pfss_fieldlines): """ Convert fieldline coordinates output from the SSW package `pfss <http://www.lmsal.com/~derosa/pfsspack/>`_ into `~astropy.coordinates.SkyCoord` objects. Parameters ---------- pfss_fieldlines : `~numpy.recarray` Structure produced by reading pfss output with `~scipy.io.readsav` Returns ------- fieldlines : `list` Each entry is a `tuple` containing a `~astropy.coordinates.SkyCoord` object and a `~astropy.units.Quantity` object listing the coordinates and field strength along the loop. """ # Fieldline coordinates num_fieldlines = pfss_fieldlines['ptr'].shape[0] fieldlines = [] for i in range(num_fieldlines): # NOTE: For an unknown reason, there are a number of invalid points for each line output # by pfss n_valid = pfss_fieldlines['nstep'][i] lon = (pfss_fieldlines['ptph'][i, :] * u.radian).to(u.deg)[:n_valid] lat = 90 * u.deg - (pfss_fieldlines['ptth'][i, :] * u.radian).to( u.deg)[:n_valid] radius = ((pfss_fieldlines['ptr'][i, :]) * const.R_sun.to(u.cm))[:n_valid] coord = SkyCoord( lon=lon, lat=lat, radius=radius, frame=HeliographicCarrington(obstime=sunpy.time.parse_time( pfss_fieldlines['now'].decode('utf-8')))) fieldlines.append(coord) # Magnetic field strengths lon_grid = (pfss_fieldlines['phi'] * u.radian - np.pi * u.radian).to( u.deg).value lat_grid = (np.pi / 2. * u.radian - pfss_fieldlines['theta'] * u.radian).to(u.deg).value radius_grid = pfss_fieldlines['rix'] * const.R_sun.to(u.cm).value B_radius = pfss_fieldlines['br'] B_lat = pfss_fieldlines['bth'] B_lon = pfss_fieldlines['bph'] # Create interpolators B_radius_interpolator = RegularGridInterpolator( (radius_grid, lat_grid, lon_grid), B_radius, bounds_error=False, fill_value=None) B_lat_interpolator = RegularGridInterpolator( (radius_grid, lat_grid, lon_grid), B_lat, bounds_error=False, fill_value=None) B_lon_interpolator = RegularGridInterpolator( (radius_grid, lat_grid, lon_grid), B_lon, bounds_error=False, fill_value=None) # Interpolate values through each line field_strengths = [] for f in fieldlines: points = np.stack([ f.spherical.distance.to(u.cm).value, f.spherical.lat.to(u.deg).value, f.spherical.lon.to(u.deg).value ], axis=1) b_r = B_radius_interpolator(points) b_lat = B_lat_interpolator(points) b_lon = B_lon_interpolator(points) field_strengths.append(np.sqrt(b_r**2 + b_lat**2 + b_lon**2) * u.Gauss) return [(l, b) for l, b in zip(fieldlines, field_strengths)]
############################################################################### # Next we will reproject both maps on to a Carrington frame of reference. # To do this we start by creating a FITS world coordinat system (WCS) header # corresponding to the output coordinate frame. # This is set deliberately low, and (at the cost of memory and processing time) # can be increased to increase the output resolution shape_out = (360, 720) ref_coord_observer = HeliographicStonyhurst(0 * u.deg, 0 * u.deg, sunpy.sun.constants.radius, obstime=eui_map.date) ref_coord = HeliographicCarrington(0 * u.deg, 0 * u.deg, sunpy.sun.constants.radius, obstime=eui_map.date, observer=ref_coord_observer) header = sunpy.map.make_fitswcs_header( shape_out, ref_coord, scale=[180 / shape_out[0], 360 / shape_out[1]] * u.deg / u.pix, projection_code="CAR") out_wcs = WCS(header) ############################################################################### # Next we reproject and add together the two maps array, footprint = reproject_and_coadd([eui_map, aia_map], out_wcs, shape_out,
def wcsaxes_heliographic_overlay(axes, grid_spacing: u.deg = 10 * u.deg, annotate=True, obstime=None, rsun=None, observer=None, system='stonyhurst', **kwargs): """ Create a heliographic overlay using `~astropy.visualization.wcsaxes.WCSAxes`. Will draw a grid and label the top axes. Parameters ---------- axes : `~astropy.visualization.wcsaxes.WCSAxes` The `~astropy.visualization.wcsaxes.WCSAxes` object to create the overlay on. grid_spacing: `~astropy.units.Quantity` Spacing for longitude and latitude grid in degrees. annotate : `bool` Passing `False` disables the axes labels and the ticks on the top and right axes. obstime : `~astropy.time.Time` The ``obstime`` to use for the grid coordinate frame. rsun : `~astropy.units.Quantity` The ``rsun`` to use for the grid coordinate frame. observer : `~astropy.coordinates.SkyCoord` The ``observer`` to use for the grid coordinate frame. Only used for Carrington coordinates. system : str Coordinate system for the grid. Must be 'stonyhurst' or 'carrington'. If 'carrington', the ``observer`` keyword argument must be specified. kwargs : Additional keyword arguments are passed to :meth:`astropy.visualization.wcsaxes.CoordinateHelper.grid`. Returns ------- `~astropy.visualization.wcsaxes.WCSAxes` The overlay object. Notes ----- Keywords are passed to `~astropy.visualization.wcsaxes.coordinates_map.CoordinatesMap.grid`. """ # Unpack spacing if isinstance(grid_spacing, u.Quantity) and grid_spacing.size == 1: lon_space = lat_space = grid_spacing elif grid_spacing.size == 2: lon_space, lat_space = grid_spacing else: raise ValueError( "grid_spacing must be a Quantity of length one or two.") if system == 'stonyhurst': overlay = axes.get_coords_overlay( HeliographicStonyhurst(obstime=obstime, rsun=rsun)) elif system == 'carrington': overlay = axes.get_coords_overlay( HeliographicCarrington(obstime=obstime, observer=observer, rsun=rsun)) else: raise ValueError( f"system must be 'stonyhurst' or 'carrington' (got '{system}')") # Set the native coordinates to be bottom and left only so they don't share # axes with the overlay. c1, c2 = axes.coords c1.set_ticks_position('bl') c2.set_ticks_position('bl') lon = overlay[0] lat = overlay[1] lon.coord_wrap = 180 lon.set_major_formatter('dd') if annotate: lon.set_axislabel(f'{system.capitalize()} Longitude', minpad=0.8) lat.set_axislabel(f'{system.capitalize()} Latitude', minpad=0.9) lon.set_ticks_position('tr') lat.set_ticks_position('tr') else: lat.set_ticks_visible(False) lon.set_ticks_visible(False) lat.set_ticklabel_visible(False) lon.set_ticklabel_visible(False) grid_kw = {'color': 'white', 'zorder': 100, 'alpha': 0.5} grid_kw.update(kwargs) # Don't plot white ticks by default (only if explicitly asked) tick_color = grid_kw['color'] if 'color' in kwargs else 'k' lon.set_ticks(spacing=lon_space, color=tick_color) lat.set_ticks(spacing=lat_space, color=tick_color) overlay.grid(**grid_kw) if axes.title: x, y = axes.title.get_position() axes.title.set_position([x, y + 0.08]) return overlay