Repository: sunpy/sunpy

Issue:
AIA FITS headers have inaccurate HGS coordinates
AIA FITS headers apparently have inaccurate Heliographic Stonyhurst (HGS) coordinates (`HGLN_OBS` and `HGLT_OBS`).  For example, the distance from Earth center does not match the orbit radius.  We currently use these keywords to generate the `observer_coordinate` for an AIA map, so we provide an inaccurate observer location.

The headers also have Heliographic Aries Ecliptic (HAE) coordinates (`HAEX_OBS`, `HAEY_OBS`, and `HAEZ_OBS`), and the HAE coordinates are inconsistent with the HGS coordinates in the same header.  We have previously verified the accuracy of SunPy's transformation from HAE to HGS (e.g., https://github.com/sunpy/sunpy/issues/2445#issuecomment-364531159).   The HAE coordinates appear to be credible, and likely should be trusted over the HGS coordinates.

My recommendation is for `AIAMap` to override the generation of `observer_coordinate` to use the HAE coordinates.  Discuss.


Discussion:
This feels like something we should get input on the AIA team from? @mbobra any suggestions on who we could talk to?
To be clear, the level of discrepancy here is essentially negligible (<1 arcsecond) for typical uses of AIA data.  However, it becomes important when placing non-solar objects (e.g., Venus) relative to the Sun in helioprojective coordinates.  We'd have to talk either to someone who can check the SDO code that calculates `HGLN_OBS`/`HGLT_OBS` for the header or to someone who has analyzed their Venus-transit data to calibrate observatory pointing.

Resolving patch:
diff --git a/sunpy/map/sources/sdo.py b/sunpy/map/sources/sdo.py
--- a/sunpy/map/sources/sdo.py
+++ b/sunpy/map/sources/sdo.py
@@ -6,6 +6,14 @@
 
 import matplotlib.pyplot as plt
 
+from astropy.coordinates import CartesianRepresentation, SkyCoord
+# Versions of Astropy that do not have HeliocentricMeanEcliptic have the same frame
+# with the incorrect name HeliocentricTrueEcliptic
+try:
+    from astropy.coordinates import HeliocentricMeanEcliptic
+except ImportError:
+    from astropy.coordinates import HeliocentricTrueEcliptic as HeliocentricMeanEcliptic
+import astropy.units as u
 from astropy.visualization.mpl_normalize import ImageNormalize
 from astropy.visualization import AsinhStretch
 
@@ -28,6 +36,13 @@ class AIAMap(GenericMap):
     211 A (Fe XIV), 304 A (He II), 335 A (Fe XVI). One telescope observes
     in the visible 1600 A (C IV) and the nearby continuun (1700 A).
 
+    Notes
+    -----
+    Observer location: The standard AIA FITS header provides the spacecraft location in multiple
+    coordinate systems, including Heliocentric Aries Ecliptic (HAE) and Heliographic Stonyhurst
+    (HGS).  SunPy uses the provided HAE coordinates due to accuracy concerns with the provided
+    HGS coordinates, but other software packages may make different choices.
+
     References
     ----------
     * `SDO Mission Page <https://sdo.gsfc.nasa.gov/>`_
@@ -47,6 +62,45 @@ def __init__(self, data, header, **kwargs):
         self.plot_settings['cmap'] = plt.get_cmap(self._get_cmap_name())
         self.plot_settings['norm'] = ImageNormalize(stretch=source_stretch(self.meta, AsinhStretch(0.01)))
 
+    @property
+    def observer_coordinate(self):
+        """
+        The Heliographic Stonyhurst Coordinate of the observer.
+
+        This coordinate is determined using the Heliocentric Aries Ecliptic (HAE) coordinates
+        in the header.
+        """
+        vector = CartesianRepresentation(self.meta['haex_obs'],
+                                         self.meta['haey_obs'],
+                                         self.meta['haez_obs'])
+        coord = SkyCoord(vector * u.m, frame=HeliocentricMeanEcliptic, obstime=self.date)
+        return coord.heliographic_stonyhurst
+
+    @property
+    def heliographic_latitude(self):
+        """Heliographic latitude."""
+        return self.observer_coordinate.lat
+
+    @property
+    def heliographic_longitude(self):
+        """Heliographic longitude."""
+        return self.observer_coordinate.lon
+
+    @property
+    def carrington_latitude(self):
+        """Carrington latitude."""
+        return self.observer_coordinate.heliographic_carrington.lat
+
+    @property
+    def carrington_longitude(self):
+        """Carrington longitude."""
+        return self.observer_coordinate.heliographic_carrington.lon
+
+    @property
+    def dsun(self):
+        """The observer distance from the Sun."""
+        return self.observer_coordinate.radius.to('m')
+
     @property
     def observatory(self):
         """


Test patch:
diff --git a/sunpy/coordinates/tests/test_utils.py b/sunpy/coordinates/tests/test_utils.py
--- a/sunpy/coordinates/tests/test_utils.py
+++ b/sunpy/coordinates/tests/test_utils.py
@@ -43,20 +43,20 @@ def test_great_arc_calculable(start, end):
 # initialization of the GreatArc object.
 @pytest.mark.parametrize("points_requested, points_expected, first_point, last_point, last_inner_angle, last_distance",
                          # Test default
-                         [(None, 100, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981272686),
+                         [(None, 100, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981299),
                           # Test int as an option
-                          (3, 3, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981272686),
+                          (3, 3, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981299),
                           # Test equally spaced monotonically increasing numpy
                           # array
-                          (np.linspace(0, 1, 43), 43, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981272686),
+                          (np.linspace(0, 1, 43), 43, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981299),
                           # Test unequally spaced monotonically increasing numpy
                           # array
-                          (np.asarray([0.1, 0.2, 0.6, 0.67, 0.99]), 5, (604.68091703, -468.64217597), (-88.83212616, 792.76284375), 1.84967446, 1287373.426146),
+                          (np.asarray([0.1, 0.2, 0.6, 0.67, 0.99]), 5, (604.68091703, -468.64217597), (-88.83212616, 792.76284375), 1.84967446, 1287373.4261486),
                           # Test unequally spaced monotonically decreasing numpy
                           # array
                           (np.asarray([0.93, 0.78, 0.3, 0.001]), 4, (-21.28208654, 743.58866798), (600.1512768 , -598.78376614), 0.00186836, 1300.37719813),
                           # Test numpy array that increases and decreases
-                          (np.asarray([0.94, 0.73, 0.8, 0.21]), 4, (-32.5852606 , 752.45507707), (585.45829119, -305.26965043), 0.39235519, 273079.21160673)])
+                          (np.asarray([0.94, 0.73, 0.8, 0.21]), 4, (-32.5852606 , 752.45507707), (585.45829119, -305.26965043), 0.39235519, 273079.2116073)])
 def test_great_arc_coordinates(points_requested, points_expected, first_point,
                                last_point, last_inner_angle, last_distance):
 
@@ -81,14 +81,14 @@ def test_great_arc_coordinates(points_requested, points_expected, first_point,
     assert gc.center.y == 0 * u.km
     assert gc.center.z == 0 * u.km
 
-    np.testing.assert_almost_equal(gc.start_cartesian, np.asarray([428721.09135385, -428722.90519236, 341776.09102756]))
-    np.testing.assert_almost_equal(gc.end_cartesian, np.asarray([-71429.52293813, 571439.07124801, 390859.57978693]))
+    np.testing.assert_almost_equal(gc.start_cartesian, np.asarray([428721.0913539, -428722.9051924, 341776.0910214]))
+    np.testing.assert_almost_equal(gc.end_cartesian, np.asarray([-71429.5229381, 571439.071248, 390859.5797815]))
     np.testing.assert_almost_equal(gc.center_cartesian, np.asarray([0, 0, 0]))
 
-    np.testing.assert_almost_equal(gc.v1, np.asarray([428721.09135385, -428722.90519236, 341776.09102756]))
-    np.testing.assert_almost_equal(gc._r, 696000.00000451738)
-    np.testing.assert_almost_equal(gc.v2, np.asarray([-71429.52293813, 571439.07124801, 390859.57978693]))
-    np.testing.assert_almost_equal(gc.v3, np.asarray([56761.62657985, 466230.70058902, 513637.08158833]))
+    np.testing.assert_almost_equal(gc.v1, np.asarray([428721.0913539, -428722.9051924, 341776.0910214]))
+    np.testing.assert_almost_equal(gc._r, 696000.000001501)
+    np.testing.assert_almost_equal(gc.v2, np.asarray([-71429.5229381, 571439.071248, 390859.5797815]))
+    np.testing.assert_almost_equal(gc.v3, np.asarray([56761.6265851, 466230.7005856, 513637.0815867]))
 
     # Inner angle
     assert gc.inner_angle.unit == u.rad
@@ -96,11 +96,11 @@ def test_great_arc_coordinates(points_requested, points_expected, first_point,
 
     # Distance
     assert gc.distance.unit == u.km
-    np.testing.assert_almost_equal(gc.distance.value, 1300377.1981272686)
+    np.testing.assert_almost_equal(gc.distance.value, 1300377.1981298963)
 
     # Radius of the sphere
     assert gc.radius.unit == u.km
-    np.testing.assert_almost_equal(gc.radius.value, 696000.0000045174)
+    np.testing.assert_almost_equal(gc.radius.value, 696000.000001501)
 
     # Test the calculation of the SkyCoords
     # Coordinates method
diff --git a/sunpy/physics/tests/test_solar_rotation.py b/sunpy/physics/tests/test_solar_rotation.py
--- a/sunpy/physics/tests/test_solar_rotation.py
+++ b/sunpy/physics/tests/test_solar_rotation.py
@@ -43,15 +43,15 @@ def aia171_test_mapsequence(aia171_test_submap):
 # Known displacements for these mapsequence layers when the layer index is set to 0
 @pytest.fixture
 def known_displacements_layer_index0():
-    return {'x': np.asarray([-0.089361,  -9.872888, -19.677882]),
-            'y': np.asarray([-0.113397,  0.137566,  0.376327])}
+    return {'x': np.asarray([-0.089361,  -10.570323, -21.075823]),
+            'y': np.asarray([-0.113397,  0.155022,  0.409435])}
 
 
 # Known displacements for these mapsequence layers when the layer index is set to 1
 @pytest.fixture
 def known_displacements_layer_index1():
-    return {'x': np.asarray([9.671631, -0.089361, -9.872888]),
-            'y': np.asarray([-0.376532, -0.113397,  0.137566])}
+    return {'x': np.asarray([10.365743, -0.089361, -10.570315]),
+            'y': np.asarray([-0.395786, -0.113397,  0.155022])}
 
 
 def test_calculate_solar_rotate_shift(aia171_test_mapsequence, known_displacements_layer_index0, known_displacements_layer_index1):
diff --git a/sunpy/tests/figure_hashes_py36.json b/sunpy/tests/figure_hashes_py36.json
--- a/sunpy/tests/figure_hashes_py36.json
+++ b/sunpy/tests/figure_hashes_py36.json
@@ -1,13 +1,13 @@
 {
     "sunpy.map.tests.test_plotting.test_draw_contours_aia": "576ce31e7cabaf98fa9edc8b802da07c5f0cad24ce6cc0498f4053db267c1d12",
-    "sunpy.map.tests.test_plotting.test_draw_grid_aia171": "ffed81ce2ecf53cb8efc53f75f0c030e328e9dedf6261ae76548c71c8b0f6402",
+    "sunpy.map.tests.test_plotting.test_draw_grid_aia171": "eb3f5b9b7317b31e7e77a278d83a4e4717077f2e996b2449909f10cf6d582c4b",
     "sunpy.map.tests.test_plotting.test_heliographic_peek": "f826ff27728903a6e5fb7eafe4754df3ea86cf033ed41756155946a0ef6c247c",
     "sunpy.map.tests.test_plotting.test_heliographic_rectangle": "e366f103b4f96946fb06da1d591b3bdb8ee027eaf387b3ff78b0bb7b5ad69124",
     "sunpy.map.tests.test_plotting.test_peek_aia171": "bfbe20f58929eb19812b9bbb2c21f4d3fbb7e53defcd00e484ef6c1a943d2222",
     "sunpy.map.tests.test_plotting.test_peek_basic_plot_aia171": "376d4acfbab17abac8d82cbbeb37eb3033281a0a15bc29c6b03c3a6a4d4da43d",
-    "sunpy.map.tests.test_plotting.test_peek_grid_aia171": "0c5b912605c7d6e08f85e4b1993dc04091c2439157089f081819c0a2defb88d1",
-    "sunpy.map.tests.test_plotting.test_peek_grid_limb_aia171": "9f2f31497ec2fd63cfe4b56dacce1675aefdb92ad344e318a9a51b0b713e2889",
-    "sunpy.map.tests.test_plotting.test_peek_grid_spacing_aia171": "9ddc7026bc6ba47bc79c039e255ed4b1b9e6fdedfbd6c73279390bcd427dab4a",
+    "sunpy.map.tests.test_plotting.test_peek_grid_aia171": "6d06cce44421a95012e62f42dec533379ce407ae7f35856ac335e7846e264d26",
+    "sunpy.map.tests.test_plotting.test_peek_grid_limb_aia171": "bedeef83c9e84c2b1bce8ce579de4d78c9228e540a5e0d4b57a142f6e23438f7",
+    "sunpy.map.tests.test_plotting.test_peek_grid_spacing_aia171": "9d8db2f2da8e8bb2b1f3fd5f321f137f9d11573a420fd3bedde8acbf963fd245",
     "sunpy.map.tests.test_plotting.test_peek_limb_aia171": "dbee8e3f01517a79b6305e03e52b3b6cdcfb4deb46f6d3d92efdcf6700e73742",
     "sunpy.map.tests.test_plotting.test_plot_aia171": "eb9784d991df9291be773eaafcdb979bccc3d9d628a88c30369c5bc8c922e2bf",
     "sunpy.map.tests.test_plotting.test_plot_aia171_clip": "d16762707b2df85108b43763ad4ee617e4267876e87782cc8b0fa11d45f7ddd0",