Repository: sunpy/sunpy

Issue:
Enable a HeliographicCarrington frame to be defined using only the Sun-observer distance
Our `HeliographicCarrington` frame is now observer-based (#3782), and the API requires a complete coordinate for the observer.  This turns out to be awkward when wanting to create a `HeliographicCarrington` coordinate representing an observer location, because the observer coordinate is seemingly needed to define the frame for the observer coordinate.  Mathematically, the `HeliographicCarrington`->`HeliographicStonyhurst` transformation needs only the Sun-observer distance, and modifies only the longitude component, so the circular loop is solely a consequence of the implementation.

There should be a way to define a `HeliographicCarrington` frame using only the Sun-observer distance, as opposed to a complete coordinate for the observer.  Either a new frame attribute can be added or our existing `ObserverCoordinateAttribute` can be extended.

Discussion:
Cadair: Looks like a good solution.
pep8speaks: Hello @ayshih! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:



* In the file [`sunpy/coordinates/frames.py`](https://github.com/sunpy/sunpy/blob/b8e99195a9a05ca9d0fef1767a9aad53cec342d7/sunpy/coordinates/frames.py):

> [Line 294:101](https://github.com/sunpy/sunpy/blob/b8e99195a9a05ca9d0fef1767a9aad53cec342d7/sunpy/coordinates/frames.py#L294): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (124 > 100 characters)











Patch:
diff --git a/changelog/4003.feature.rst b/changelog/4003.feature.rst
deleted file mode 100644
index c8cb21abf..000000000
--- a/changelog/4003.feature.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Added a context manager (:meth:`~sunpy.coordinates.frames.Helioprojective.assume_spherical_screen`) to interpret `~sunpy.coordinates.frames.Helioprojective` coordinates as being on the inside of a spherical screen instead of on the surface of the Sun.
diff --git a/changelog/4659.feature.rst b/changelog/4659.feature.rst
new file mode 100644
index 000000000..886e255f5
--- /dev/null
+++ b/changelog/4659.feature.rst
@@ -0,0 +1,2 @@
+The `~sunpy.coordinates.frames.HeliographicCarrington` frame now accepts the specification of ``observer='self'`` to indicate that the coordinate itself is also the observer for the coordinate frame.
+This functionality greatly simplifies working with locations of observatories that are provided in Carrington coordinates.
diff --git a/docs/code_ref/coordinates/index.rst b/docs/code_ref/coordinates/index.rst
index a70293362..f6df19a47 100644
--- a/docs/code_ref/coordinates/index.rst
+++ b/docs/code_ref/coordinates/index.rst
@@ -240,6 +240,8 @@ transformations cannot be performed.
 The location of the observer is automatically populated from meta data when
 coordinate frames are created using map.
 
+In the case of `~sunpy.coordinates.frames.HeliographicCarrington`, one can specify ``observer='self'`` to indicate that the coordinate itself should be used as the observer for defining the coordinate frame.
+
 It is possible to convert from a `~sunpy.coordinates.frames.Helioprojective`
 frame with one observer location to another
 `~sunpy.coordinates.frames.Helioprojective` frame with a different observer
diff --git a/docs/guide/installation/index.rst b/docs/guide/installation/index.rst
index 809844ba8..5110d64ff 100644
--- a/docs/guide/installation/index.rst
+++ b/docs/guide/installation/index.rst
@@ -11,7 +11,7 @@ Requirements
 These are the minimum versions of packages needed to install sunpy:
 
 - python 3.7
-- astropy 4.0.3
+- astropy 4.0
 - scipy 1.2
 - matplotlib 2.2.2
 - pandas 0.23
diff --git a/examples/map_transformations/reprojection_spherical_screen.py b/examples/map_transformations/reprojection_spherical_screen.py
deleted file mode 100644
index 9f2a6eda2..000000000
--- a/examples/map_transformations/reprojection_spherical_screen.py
+++ /dev/null
@@ -1,108 +0,0 @@
-"""
-=====================================
-Reprojecting Using a Spherical Screen
-=====================================
-
-This example demonstrates how you can reproject an image as if it lies on the
-inside of a spherical screen and the observer is not at the center of the
-sphere.  This functionality is primarily for visualization purposes, since
-features in the image are unlikely to actually lie on this spherical screen.
-
-You will need `reproject <https://reproject.readthedocs.io/en/stable/>`__ v0.6
-or higher installed.
-"""
-# sphinx_gallery_thumbnail_number = 4
-
-import matplotlib.pyplot as plt
-from reproject import reproject_interp
-
-import astropy.units as u
-from astropy.coordinates import SkyCoord
-from astropy.wcs import WCS
-
-import sunpy.map
-from sunpy.coordinates import Helioprojective
-from sunpy.data.sample import AIA_171_IMAGE
-
-######################################################################
-# We will use one of the AIA images from the sample data.  We fix the
-# range of values for the Map's normalizer.
-
-aia_map = sunpy.map.Map(AIA_171_IMAGE)
-aia_map.plot_settings['norm'].vmin = 0
-aia_map.plot_settings['norm'].vmax = 10000
-
-plt.figure()
-plt.subplot(projection=aia_map)
-aia_map.plot()
-
-######################################################################
-# Let's define a new observer that is well separated from Earth.
-
-new_observer = SkyCoord(70*u.deg, 20*u.deg, 1*u.AU, obstime=aia_map.date,
-                        frame='heliographic_stonyhurst')
-
-######################################################################
-# Create a WCS header for this new observer using helioprojective
-# coordinates, and create the corresponding `~astropy.wcs.WCS` object.
-
-out_shape = aia_map.data.shape
-
-out_ref_coord = SkyCoord(0*u.arcsec, 0*u.arcsec, obstime=new_observer.obstime,
-                         frame='helioprojective', observer=new_observer)
-out_header = sunpy.map.make_fitswcs_header(
-    out_shape,
-    out_ref_coord,
-    scale=u.Quantity(aia_map.scale),
-    rotation_matrix=aia_map.rotation_matrix,
-    instrument=aia_map.instrument,
-    wavelength=aia_map.wavelength
-)
-out_wcs = WCS(out_header)
-
-######################################################################
-# If you reproject the AIA Map to the perspective of the new observer,
-# the default assumption is that the image lies on the surface of the
-# Sun.  However, the parts of the image beyond the solar disk cannot
-# be mapped to the surface of the Sun, and thus do not show up in the
-# output.
-
-output, _ = reproject_interp(aia_map, out_wcs, out_shape)
-outmap_default = sunpy.map.Map((output, out_header))
-outmap_default.plot_settings = aia_map.plot_settings
-
-plt.figure()
-plt.subplot(projection=outmap_default)
-outmap_default.plot()
-
-######################################################################
-# You can use the different assumption that the image lies on the
-# surface of a spherical screen centered at AIA, with a radius equal
-# to the Sun-AIA distance.  The curvature of the spherical screen is
-# not obvious in this plot due to the relatively small field of view
-# of AIA (compared to, say, a coronagraph).
-
-with Helioprojective.assume_spherical_screen(aia_map.observer_coordinate):
-    output, _ = reproject_interp(aia_map, out_wcs, out_shape)
-outmap_screen_all = sunpy.map.Map((output, out_header))
-outmap_screen_all.plot_settings = aia_map.plot_settings
-
-plt.figure()
-plt.subplot(projection=outmap_screen_all)
-outmap_screen_all.plot()
-
-######################################################################
-# Finally, you can specify that the spherical-screen assumption should
-# be used for only off-disk parts of the image, and continue to map
-# on-disk parts of the image to the surface of the Sun.
-
-with Helioprojective.assume_spherical_screen(aia_map.observer_coordinate,
-                                             only_off_disk=True):
-    output, _ = reproject_interp(aia_map, out_wcs, out_shape)
-outmap_screen_off_disk = sunpy.map.Map((output, out_header))
-outmap_screen_off_disk.plot_settings = aia_map.plot_settings
-
-plt.figure()
-plt.subplot(projection=outmap_screen_off_disk)
-outmap_screen_off_disk.plot()
-plt.show()
diff --git a/setup.cfg b/setup.cfg
index fe3175326..c326941e5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -37,7 +37,7 @@ install_requires =
   scipy>=1.2.0
   matplotlib>=2.2.2
   pandas>=0.23.0
-  astropy>=4.0.3
+  astropy>=4.0
   parfive[ftp]>=1.1.0
   h5netcdf>=0.8.1
 
diff --git a/sunpy/coordinates/frameattributes.py b/sunpy/coordinates/frameattributes.py
index c0d5bcd81..387bc175c 100644
--- a/sunpy/coordinates/frameattributes.py
+++ b/sunpy/coordinates/frameattributes.py
@@ -135,7 +135,7 @@ class ObserverCoordinateAttribute(CoordinateAttribute):
             # If the observer is a string and we have obstime then calculate
             # the position of the observer.
             if isinstance(observer, str):
-                if obstime is not None:
+                if observer != "self" and obstime is not None:
                     new_observer = self._convert_string_to_coord(observer.lower(), obstime)
                     new_observer.object_name = observer
                     setattr(instance, '_' + self.name, new_observer)
diff --git a/sunpy/coordinates/frames.py b/sunpy/coordinates/frames.py
index 23504f40d..dbb58d52f 100644
--- a/sunpy/coordinates/frames.py
+++ b/sunpy/coordinates/frames.py
@@ -4,8 +4,6 @@ Common solar physics coordinate systems.
 This submodule implements various solar physics coordinate frames for use with
 the `astropy.coordinates` module.
 """
-from contextlib import contextmanager
-
 import numpy as np
 
 import astropy.units as u
@@ -261,6 +259,12 @@ class HeliographicCarrington(BaseHeliographic):
         HeliographicCarrington(lon, lat, obstime=obstime, observer=observer)
         HeliographicCarrington(lon, lat, radius, obstime=obstime, observer=observer)
 
+    If you want to define the location in HGC such that the observer for the coordinate frame is
+    the same as that location (e.g., the location of an observatory in its corresponding HGC
+    frame), use ``observer='self'``::
+
+        HeliographicCarrington(lon, lat, radius, obstime=obstime, observer='self')
+
     Parameters
     ----------
     {data}
@@ -283,9 +287,11 @@ class HeliographicCarrington(BaseHeliographic):
         (1., 2., 3.)>
 
     >>> sc = SkyCoord([1,2,3]*u.deg, [4,5,6]*u.deg, [5,6,7]*u.km,
-    ...               obstime="2010/01/01T00:00:45", frame="heliographic_carrington")
+    ...               obstime="2010/01/01T00:00:45",
+    ...               observer="self",
+    ...               frame="heliographic_carrington")
     >>> sc
-    <SkyCoord (HeliographicCarrington: obstime=2010-01-01T00:00:45.000, observer=None): (lon, lat, radius) in (deg, deg, km)
+    <SkyCoord (HeliographicCarrington: obstime=2010-01-01T00:00:45.000, observer=self): (lon, lat, radius) in (deg, deg, km)
         [(1., 4., 5.), (2., 5., 6.), (3., 6., 7.)]>
 
     >>> sc = SkyCoord(CartesianRepresentation(0*u.km, 45*u.km, 2*u.km),
@@ -465,96 +471,17 @@ class Helioprojective(SunPyBaseCoordinateFrame):
 
         rep = self.represent_as(UnitSphericalRepresentation)
         lat, lon = rep.lat, rep.lon
-        cos_alpha = np.cos(lat) * np.cos(lon)
+        alpha = np.arccos(np.cos(lat) * np.cos(lon)).to(lat.unit)
         c = self.observer.radius**2 - self.rsun**2
-        b = -2 * self.observer.radius * cos_alpha
-        # Ignore sqrt of NaNs
+        b = -2 * self.observer.radius * np.cos(alpha)
+        # Ingore sqrt of NaNs
         with np.errstate(invalid='ignore'):
-            d = ((-1*b) - np.sqrt(b**2 - 4*c)) / 2  # use the "near" solution
-
-        if self._spherical_screen:
-            sphere_center = self._spherical_screen['center'].transform_to(self).cartesian
-            c = sphere_center.norm()**2 - self._spherical_screen['radius']**2
-            b = -2 * sphere_center.dot(rep)
-            # Ignore sqrt of NaNs
-            with np.errstate(invalid='ignore'):
-                dd = ((-1*b) + np.sqrt(b**2 - 4*c)) / 2  # use the "far" solution
-
-            d = np.fmin(d, dd) if self._spherical_screen['only_off_disk'] else dd
+            d = ((-1*b) - np.sqrt(b**2 - 4*c)) / 2
 
         return self.realize_frame(SphericalRepresentation(lon=lon,
                                                           lat=lat,
                                                           distance=d))
 
-    _spherical_screen = None
-
-    @classmethod
-    @contextmanager
-    def assume_spherical_screen(cls, center, only_off_disk=False):
-        """
-        Context manager to interpret 2D coordinates as being on the inside of a spherical screen.
-
-        The radius of the screen is the distance between the specified `center` and Sun center.
-        This `center` does not have to be the same as the observer location for the coordinate
-        frame.  If they are the same, then this context manager is equivalent to assuming that the
-        helioprojective "zeta" component is zero.
-
-        This replaces the default assumption where 2D coordinates are mapped onto the surface of the
-        Sun.
-
-        Parameters
-        ----------
-        center : `~astropy.coordinates.SkyCoord`
-            The center of the spherical screen
-        only_off_disk : `bool`, optional
-            If `True`, apply this assumption only to off-disk coordinates, with on-disk coordinates
-            still mapped onto the surface of the Sun.  Defaults to `False`.
-
-        Examples
-        --------
-
-        .. minigallery:: sunpy.coordinates.Helioprojective.assume_spherical_screen
-
-        >>> import astropy.units as u
-        >>> from sunpy.coordinates import Helioprojective
-        >>> h = Helioprojective(range(7)*u.arcsec*319, [0]*7*u.arcsec,
-        ...                     observer='earth', obstime='2020-04-08')
-        >>> print(h.make_3d())
-        <Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
-            [(   0., 0., 0.99660825), ( 319., 0., 0.99687244),
-             ( 638., 0., 0.99778472), ( 957., 0., 1.00103285),
-             (1276., 0.,        nan), (1595., 0.,        nan),
-             (1914., 0.,        nan)]>
-
-        >>> with Helioprojective.assume_spherical_screen(h.observer):
-        ...     print(h.make_3d())
-        <Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
-            [(   0., 0., 1.00125872), ( 319., 0., 1.00125872),
-             ( 638., 0., 1.00125872), ( 957., 0., 1.00125872),
-             (1276., 0., 1.00125872), (1595., 0., 1.00125872),
-             (1914., 0., 1.00125872)]>
-
-        >>> with Helioprojective.assume_spherical_screen(h.observer, only_off_disk=True):
-        ...     print(h.make_3d())
-        <Helioprojective Coordinate (obstime=2020-04-08T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
-            [(   0., 0., 0.99660825), ( 319., 0., 0.99687244),
-             ( 638., 0., 0.99778472), ( 957., 0., 1.00103285),
-             (1276., 0., 1.00125872), (1595., 0., 1.00125872),
-             (1914., 0., 1.00125872)]>
-        """
-        try:
-            old_spherical_screen = cls._spherical_screen  # nominally None
-
-            center_hgs = center.transform_to(HeliographicStonyhurst(obstime=center.obstime))
-            cls._spherical_screen = {
-                'center': center,
-                'radius': center_hgs.radius,
-                'only_off_disk': only_off_disk
-            }
-            yield
-        finally:
-            cls._spherical_screen = old_spherical_screen
-
 
 @add_common_docstring(**_frame_parameters())
 class HeliocentricEarthEcliptic(SunPyBaseCoordinateFrame):
diff --git a/sunpy/coordinates/transformations.py b/sunpy/coordinates/transformations.py
index 686259af6..135eef982 100644
--- a/sunpy/coordinates/transformations.py
+++ b/sunpy/coordinates/transformations.py
@@ -200,9 +200,13 @@ def _observers_are_equal(obs_1, obs_2):
                            "requires the destination observer to be specified, as the "
                            f"source observer is set to {obs_1}.")
     if isinstance(obs_1, str):
+        if obs_1 == "self":
+            return False
         raise ConvertError("The source observer needs to have `obstime` set because the "
                            "destination observer is different.")
     if isinstance(obs_2, str):
+        if obs_2 == "self":
+            return False
         raise ConvertError("The destination observer needs to have `obstime` set because the "
                            "source observer is different.")
 
@@ -217,9 +221,13 @@ def _check_observer_defined(frame):
         raise ConvertError("This transformation cannot be performed because the "
                            f"{frame.__class__.__name__} frame has observer=None.")
     elif isinstance(frame.observer, str):
-        raise ConvertError("This transformation cannot be performed because the "
-                           f"{frame.__class__.__name__} frame needs a specified obstime "
-                           f"to fully resolve observer='{frame.observer}'.")
+        if frame.observer != "self":
+            raise ConvertError("This transformation cannot be performed because the "
+                               f"{frame.__class__.__name__} frame needs a specified obstime "
+                               f"to fully resolve observer='{frame.observer}'.")
+        elif not isinstance(frame, HeliographicCarrington):
+            raise ConvertError(f"The {frame.__class__.__name__} frame has observer='self' "
+                               "but this is valid for only HeliographicCarrington frames.")
 
 
 # =============================================================================
@@ -275,12 +283,16 @@ def hgs_to_hgc(hgscoord, hgcframe):
     Convert from Heliographic Stonyhurst to Heliographic Carrington.
     """
     _check_observer_defined(hgcframe)
+    if isinstance(hgcframe.observer, str) and hgcframe.observer == "self":
+        observer_radius = hgscoord.radius
+    else:
+        observer_radius = hgcframe.observer.radius
 
     # First transform the HGS coord to the HGC obstime
     int_coord = _transform_obstime(hgscoord, hgcframe.obstime)
 
     # Rotate from HGS to HGC
-    total_matrix = _rotation_matrix_hgs_to_hgc(int_coord.obstime, hgcframe.observer.radius)
+    total_matrix = _rotation_matrix_hgs_to_hgc(int_coord.obstime, observer_radius)
     newrepr = int_coord.cartesian.transform(total_matrix)
 
     return hgcframe._replicate(newrepr, obstime=int_coord.obstime)
@@ -294,13 +306,17 @@ def hgc_to_hgs(hgccoord, hgsframe):
     Convert from Heliographic Carrington to Heliographic Stonyhurst.
     """
     _check_observer_defined(hgccoord)
+    if isinstance(hgccoord.observer, str) and hgccoord.observer == "self":
+        observer_radius = hgccoord.radius
+    else:
+        observer_radius = hgccoord.observer.radius
 
     # First transform the HGC coord to the HGS obstime
     int_coord = _transform_obstime(hgccoord, hgsframe.obstime)
 
     # Rotate from HGC to HGS
     total_matrix = matrix_transpose(_rotation_matrix_hgs_to_hgc(int_coord.obstime,
-                                                                hgccoord.observer.radius))
+                                                                observer_radius))
     newrepr = int_coord.cartesian.transform(total_matrix)
 
     return hgsframe._replicate(newrepr, obstime=int_coord.obstime)
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
index 90e891ef8..d1bedc366 100644
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -857,22 +857,10 @@ class GenericMap(NDData):
             if all(meta_list):
                 sc = SkyCoord(obstime=self.date, **kwargs)
 
-                # We need to specially handle an observer location provided in Carrington
-                # coordinates.  To create the observer coordinate, we need to specify the
-                # frame, but defining a Carrington frame normally requires specifying the
-                # frame's observer.  This loop is the problem.  Instead, since the
-                # Carrington frame needs only the Sun-observer distance component from the
-                # frame's observer, we create the same frame using a fake observer that has
-                # the same Sun-observer distance.
+                # If the observer location is supplied in Carrington coordinates,
+                # the coordinate's `observer` attribute should be set to "self"
                 if isinstance(sc.frame, HeliographicCarrington):
-                    fake_observer = HeliographicStonyhurst(0*u.deg, 0*u.deg, sc.radius,
-                                                           obstime=sc.obstime)
-                    fake_frame = sc.frame.replicate(observer=fake_observer)
-                    hgs = fake_frame.transform_to(HeliographicStonyhurst(obstime=sc.obstime))
-
-                    # HeliographicStonyhurst doesn't need an observer, but adding the observer
-                    # facilitates a conversion back to HeliographicCarrington
-                    return SkyCoord(hgs, observer=hgs)
+                    sc.frame._observer = "self"
 
                 return sc.heliographic_stonyhurst
             elif any(meta_list) and not set(keys).isdisjoint(self.meta.keys()):


Test patch:
diff --git a/sunpy/coordinates/tests/test_frameattributes.py b/sunpy/coordinates/tests/test_frameattributes.py
index 0cd5fd3a5..4a9d6cf54 100644
--- a/sunpy/coordinates/tests/test_frameattributes.py
+++ b/sunpy/coordinates/tests/test_frameattributes.py
@@ -9,7 +9,7 @@ from astropy.time import Time
 from sunpy.coordinates import frames, get_earth
 from sunpy.time import parse_time
 from ..frameattributes import ObserverCoordinateAttribute, TimeFrameAttributeSunPy
-from ..frames import HeliocentricInertial, HeliographicStonyhurst, Helioprojective
+from ..frames import HeliocentricInertial, HeliographicCarrington, HeliographicStonyhurst, Helioprojective
 
 
 @pytest.fixture
@@ -155,6 +155,14 @@ def test_coord_get():
     assert str(obs) == "<HeliographicStonyhurst Coordinate for 'mars'>"
 
 
+def test_observer_self_get():
+    hgc_noobstime = HeliographicCarrington(observer="self")
+    assert hgc_noobstime.observer == "self"
+
+    hgc_obstime = HeliographicCarrington(observer="self", obstime="2001-01-01")
+    assert hgc_obstime.observer == "self"
+
+
 def test_default_hcc_observer():
     h = frames.Heliocentric()
     assert h.observer is None
diff --git a/sunpy/coordinates/tests/test_transformations.py b/sunpy/coordinates/tests/test_transformations.py
index b2fdb0842..24bec7950 100644
--- a/sunpy/coordinates/tests/test_transformations.py
+++ b/sunpy/coordinates/tests/test_transformations.py
@@ -372,6 +372,51 @@ def test_hgc_hgc_different_observers():
     assert_quantity_allclose(sc_hgc_mars.lon - sc_hgc_sun.lon, ltt_mars * sidereal_rotation_rate)
 
 
+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_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_hcc_hcc():
     # Test same observer and changing obstime
     observer = HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU, obstime='2001-02-01')