utu.rotation.rotate#
- utu.rotation.rotate(position, time, time_out, num=None, off_disk='nan', observer=None, observer_out=None)[source]#
Differentially rotate helioprojective coordinates to a new time.
Each point is carried along the solar surface, which rotates faster at the equator than at the poles, and is then viewed from where the observer has moved to. This is
sunpy.coordinates.propagate_with_solar_surface()expressed in named arrays.- Parameters:
position (AbstractCartesian2dVectorArray) – The helioprojective coordinates to rotate.
time (Time | AbstractScalar) – The time at which each coordinate was observed. Broadcast against position, so a raster may give one time per step.
time_out (Time) – The time to rotate the coordinates to.
num (None | int) – The number of times at which to evaluate the rotation exactly. The rest is interpolated. See the notes below. If
None(the default), enough of them are used to space them an hour apart, which is one for a raster and a few hundred for an observation which follows a feature across the disk.off_disk (Literal['nan', 'static']) – What to do with points which miss the solar disk, and so have no surface to be carried along. If
"nan"(the default), they come back as NaN. If"static", they come back where they were given, which keeps material above the limb in a mosaic instead of discarding it.observer (None | SkyCoord) – The observer at time. If
None, the Earth is used.observer_out (None | SkyCoord) – The observer at time_out. If
None, the Earth is used.
- Return type:
Notes
Points beyond the limb have no answer. Differential rotation moves a point along the solar surface, and a line of sight which misses the Sun never reaches that surface.
sunpyreturns NaN for these, which is whatoff_disk="nan"passes on.off_disk="static"returns them unmoved instead. That is the useful thing to do when assembling a mosaic, where the alternative is to throw away every spicule and prominence above the limb, but the two kinds of point then mean different things: on-disk ones have been carried to time_out and off-disk ones are still where they were seen. Nothing marks which is which in the result, so prefer the default unless the off-limb material is wanted.The remaining option,
sunpy.coordinates.SphericalScreen, is not offered here. It answers a different question, putting every point on a sphere through the observer, and it moves on-disk results by tens of arcseconds rather than leaving them alone.The cost of a rotation is set by the number of distinct times, not by the number of points:
sunpyspends about 20 ms on a transform however many points it is given, but repeats that work for every time. Rotating a 400-step raster honestly, one transform per step, therefore takes a few seconds, nearly all of it overhead.Since the rotation of any one point is smooth and nearly linear in time, this function instead rotates every point at num times spanning the range of time, and interpolates each point between the two surrounding ones.
What that costs in accuracy is set by how far apart those times are, not by how many of them there are, and it grows as the square of the spacing. A fixed num is therefore only ever right for one length of observation. Against an exact calculation at every step, three times spanning the range are worth 0.004 arcsec over an hour but 0.22 arcsec over a day and 25 arcsec over a week, which is a way to be badly and quietly wrong about a long observation. Holding the spacing at an hour instead costs 0.002 arcsec over a day and 0.010 arcsec over a disk transit.
So num is chosen from the span of time unless it is given, and what it is chosen to be is
_spacingapart. A raster wants two, a mosaic taking a day wants twenty-five, and following a feature from one limb to the other wants a few hundred, which is the right price for the answer. Passnum=1to skip the interpolation when every point shares a time.Examples
Take a grid of points across the disk and rotate it forward by a day, drawing a line from where each point was to where the rotation puts it. The lines are shortest near the limb, where the motion is mostly toward the observer and hardly changes where a point appears to be, and shorter toward the poles, which turn more slowly than the equator.
import astropy.time import astropy.units as u import matplotlib.pyplot as plt import named_arrays as na import sunpy.coordinates.sun import utu time = astropy.time.Time("2026-09-02T00:00") radius = sunpy.coordinates.sun.angular_radius(time) start = na.Cartesian2dVectorArray( x=na.linspace(-600, 600, axis="x", num=7) * u.arcsec, y=na.linspace(-600, 600, axis="y", num=7) * u.arcsec, ) end = utu.rotation.rotate( position=start, time=time, time_out=time + 1 * u.day, ) # the two ends of each line, along an axis of their own track = na.Cartesian2dVectorArray( x=na.stack([na.broadcast_to(start.x, end.shape), end.x], axis="end"), y=na.stack([na.broadcast_to(start.y, end.shape), end.y], axis="end"), ) fig, ax = plt.subplots(figsize=(5, 5), constrained_layout=True) ax.add_patch(plt.Circle((0, 0), radius.to_value(u.arcsec), color="0.95")) na.plt.plot(track.x, track.y, ax=ax, axis="end", color="tab:blue") na.plt.scatter(start.x, start.y, ax=ax, s=10, color="black") ax.set_aspect("equal") ax.set_xlabel(f"helioprojective $x$ ({u.arcsec:latex_inline})") ax.set_ylabel(f"helioprojective $y$ ({u.arcsec:latex_inline})");
Which is what makes the rotation differential: a meridian, whose points all start at the same longitude, does not stay straight.
meridian = na.Cartesian2dVectorArray( x=0 * u.arcsec, y=na.linspace(-850, 850, axis="y", num=25) * u.arcsec, ) fig, ax = plt.subplots(figsize=(5, 5), constrained_layout=True) ax.add_patch(plt.Circle((0, 0), radius.to_value(u.arcsec), color="0.95")) for day in range(5): rotated = utu.rotation.rotate( position=meridian, time=time, time_out=time + day * u.day, ) na.plt.plot(rotated.x, rotated.y, ax=ax, axis="y", label=f"{day} d") ax.legend() ax.set_aspect("equal") ax.set_xlabel(f"helioprojective $x$ ({u.arcsec:latex_inline})") ax.set_ylabel(f"helioprojective $y$ ({u.arcsec:latex_inline})");