Repository: sunpy/sunpy

Issue:
Add a helpful warning message when converting a 2D `Helioprojective` coordinate will return all NaNs
If a 2D `Helioprojective` coordinate is off-disk, the conversion from 2D to 3D that is required for any non-trivial coordinate transformation will return all NaNs, unless something like `Helioprojective.assume_spherical_screen()` is used.  This is a recurring source of confusion for our users, particularly because they also sometimes fail to realize that they are asking for a non-trivial coordinate transformation.

We can add a helpful warning message when `Helioprojective.make_3d()` is going to turn a coordinate array that is not all NaNs into a coordinate array that is all NaNs*.  It would inform the user that a non-trivial transformation may have been requested and that a user may wish to use `Helioprojective.assume_spherical_screen()`.

\* We would not emit the warning if only some of the elements of a coordinate array turn into NaNs, because this happens all the time in mass transformations (e.g., with `reproject`).

See #5598 for a somewhat related issue


Discussion:
I can try to do this! Do you know exactly where the offending code is? I see a transformations.py that references Helioprojective in coordinates but I'm not sure where Helioprojective itself is. Sorry, I'm a bit new at this 😅 
The new code needs to go near the end of this method: https://github.com/sunpy/sunpy/blob/528579df0a4c938c133bd08971ba75c131b189a7/sunpy/coordinates/frames.py#L539-L592

If `d` is all NaNs, then the warning should be emitted.
Thanks! I'll be on it shortly.

Resolving patch:
diff --git a/sunpy/coordinates/frames.py b/sunpy/coordinates/frames.py
--- a/sunpy/coordinates/frames.py
+++ b/sunpy/coordinates/frames.py
@@ -4,6 +4,8 @@
 This submodule implements various solar physics coordinate frames for use with
 the `astropy.coordinates` module.
 """
+import re
+import traceback
 from contextlib import contextmanager
 
 import numpy as np
@@ -587,6 +589,18 @@ def make_3d(self):
 
             d = np.fmin(d, dd) if self._spherical_screen['only_off_disk'] else dd
 
+        # This warning can be triggered in specific draw calls when plt.show() is called
+        # we can not easily prevent this, so we check the specific function is being called
+        # within the stack trace.
+        stack_trace = traceback.format_stack()
+        matching_string = 'wcsaxes.*_draw_grid'
+        bypass = any([re.search(matching_string, string) for string in stack_trace])
+        if not bypass and np.all(np.isnan(d)) and np.any(np.isfinite(cos_alpha)):
+            warn_user("The conversion of these 2D helioprojective coordinates to 3D is all NaNs "
+                      "because off-disk coordinates need an additional assumption to be mapped to "
+                      "calculate distance from the observer. Consider using the context manager "
+                      "`Helioprojective.assume_spherical_screen()`.")
+
         return self.realize_frame(SphericalRepresentation(lon=lon,
                                                           lat=lat,
                                                           distance=d))
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -2154,7 +2154,6 @@ def draw_limb(self, axes=None, *, resolution=1000, **kwargs):
         if not axes:
             axes = wcsaxes_compat.gca_wcs(self.wcs)
         is_wcsaxes = wcsaxes_compat.is_wcsaxes(axes)
-
         if is_wcsaxes:
             # TODO: supply custom limb radius
             return sunpy.visualization.limb.draw_limb(
@@ -2165,7 +2164,6 @@ def draw_limb(self, axes=None, *, resolution=1000, **kwargs):
                             "and will be removed in sunpy 4.1.")
             # If not WCSAxes use a Circle
             c_kw.setdefault('radius', self.rsun_obs.value)
-
             circ = patches.Circle([0, 0], **c_kw)
             axes.add_artist(circ)
             return circ, None
@@ -2364,7 +2362,6 @@ def peek(self, draw_limb=False, draw_grid=False,
 
         if draw_limb:
             self.draw_limb(axes=axes)
-
         if draw_grid:
             if grid_spacing is None:
                 self.draw_grid(axes=axes)


Test patch:
diff --git a/sunpy/coordinates/tests/test_frames.py b/sunpy/coordinates/tests/test_frames.py
--- a/sunpy/coordinates/tests/test_frames.py
+++ b/sunpy/coordinates/tests/test_frames.py
@@ -176,14 +176,13 @@ def test_hpc_distance_off_limb():
     assert hpc1.Tx == 1500 * u.arcsec
     assert hpc1.Ty == 0 * u.arcsec
 
-    hpc2 = hpc1.make_3d()
-
-    assert isinstance(hpc2._data, SphericalRepresentation)
-
-    # Check the attrs are correct
-    assert hpc2.Tx == 1500 * u.arcsec
-    assert hpc2.Ty == 0 * u.arcsec
-    assert_quantity_allclose(hpc2.distance, u.Quantity(np.nan, u.km))
+    with pytest.raises(SunpyUserWarning, match="is all NaNs"):
+        hpc2 = hpc1.make_3d()
+        assert isinstance(hpc2._data, SphericalRepresentation)
+        # Check the attrs are correct
+        assert hpc2.Tx == 1500 * u.arcsec
+        assert hpc2.Ty == 0 * u.arcsec
+        assert_quantity_allclose(hpc2.distance, u.Quantity(np.nan, u.km))
 
 
 def test_hpc_distance_3D():
@@ -414,17 +413,14 @@ def test_skycoord_hpc(args, kwargs):
     Test that when instantiating a HPC frame with SkyCoord that make_3d
     still works.
     """
-
     sc = SkyCoord(*args, **kwargs, frame="helioprojective",
                   observer='earth', obstime="2011-01-01T00:00:00")
     # Test the transform to HGS because it will force a `make_3d` call.
-    #
-    # astropy emits some warnings here because of invalid NaN comparisons,
-    # which will be removed in a future astropy release
-    # (see https://github.com/astropy/astropy/pull/9843), so filter the warnings out.
+    # Only 1 of the 7 parameterized arguments actually emits the warning
     with warnings.catch_warnings():
-        warnings.filterwarnings("ignore", message='invalid value encountered',
-                                category=RuntimeWarning)
+        warnings.filterwarnings("ignore", message=(
+            'The conversion of these 2D helioprojective coordinates to '
+        ), category=SunpyUserWarning)
         hgs = sc.transform_to("heliographic_stonyhurst")
 
     assert isinstance(hgs.frame, HeliographicStonyhurst)