Repository: sunpy/sunpy

Issue:
Solar radius, SoHO and distances - Not to merge!!! yet.
This is a couple of changes I'm proposing after the [discussion we had today on the mailing list](https://groups.google.com/d/msg/sunpy-dev/W6nETQRsC7w/hdRHkmxxYD4J) and this pull request is mainly for discussion on how we handle this issue.

I've started to wonder whether we need the SOHO keyword (or something of the kind) or not, so when we compare images from different spacecraft it does the conversion of arcsecs properly.  Because 1 arcsec from Earth is not the same that 1 arcsec from SoHO.
![rsun](https://f.cloud.github.com/assets/963242/392375/4e6f248e-a77c-11e2-9919-85fe8e66468f.png)

dsun (a parameter in map) should take care of this, so in principle the SOHO keyword would not be needed. But dx and dy should change to a singular point of view when comparing different maps. 

The changes I've made are mainly on the angular_size function which gives just half-sun and it could be misleading as for me the angular_size of the sun is 30 arcminutes, this is, the whole Sun.  This change of name could break few things...

Also, I've used constant.radius, that it has a reference to a paper, instead of a value that we don't know whether comes from: 959.63 which it's used in [sswidl](http://hesperia.gsfc.nasa.gov/ssw/gen/idl/solar/get_sun.pro) and it's mentioned [in a page I've found](http://astro.sci.muni.cz/pub/hollan/a_papers/working/sun_rad.htm).

Now, the big debate! do we do our way or we follow SSW?



Discussion:
MDI is made such it takes an average value of Rsun, is that done so for some reason?

All of this should be taken care of by WCS. It can/should be able to handle multiple sparecraft point of views. In fact, the original IDL version was originally written for this explicit purpose (handling STEREO images).

As far as I am aware, SoHo headers pre-date fully WCS compliance. Therefore more fiddliling is required?

@Cadair that's right.

The semiradius is calculated in pb0r too, as seen from Earth.  Maybe we need a place to keep code for files that are not WCS compliant, and then fall back on that if need be.  Worst thing would be for many different pieces of the SunPy codebase to do the same thing, all slightly differently, for no reason.

We do. WCS does its thing and if it can't then it is supposed to use the sun module to figure things out.

In this new updated I've removed the tan/atan functions when it's clear that the angle is very small (but I've updated all the math.trigonometric functions to the equivalent in numpy in the sun file.

Also, dsunAtSoho has been externalized (but still within soho) so it can be used in MDI and EIT... though I'm not sure this is the right way to externalize functions.  Also, I'm not sure whether that equation applies to any point of view... that relationship is clear to me in a plane (as shown in the image above) but not sure from different point of views, if so, then it should be completely externalized and use the tan function.  In any case I need to check how much do we need that if wcs is used and all new missions are wcs compliant.

Nevertheless, I've check whether solar orbiter (0.21 AU) or solar probe plus (0.034 AU) goes even that close to the sun to see if there's any need to don't use the approximation tan x ~ x, and it stills apply

``` python
distance = np.linspace(constants.radius, constants.au, 1000)
tan_beta = [constants.radius / x  for x in distance]
plot(distance / constants.radius, tan_beta - np.arctan(tan_beta))

```

This shows that the difference starts to when we are closer than 6 R_S

@dpshelio time to rebase like a crazy person!!

@Cadair I've done the rebase from the latest staging.  I don't understand now how I got so many files changed, different with the actual staging.


Resolving patch:
diff --git a/sunpy/map/sources/soho.py b/sunpy/map/sources/soho.py
--- a/sunpy/map/sources/soho.py
+++ b/sunpy/map/sources/soho.py
@@ -15,6 +15,25 @@
 
 __all__ = ['EITMap', 'LASCOMap', 'MDIMap']
 
+def _dsunAtSoho(date, rad_d, rad_1au = None):
+    """Determines the distance to the Sun from SOhO following
+    d_{\sun,Object} =
+            D_{\sun\earth} \frac{\tan(radius_{1au}[rad])}{\tan(radius_{d}[rad])}
+    though tan x ~ x for x << 1
+    d_{\sun,Object} =
+            D_{\sun\eart} \frac{radius_{1au}[rad]}{radius_{d}[rad]}
+    since radius_{1au} and radius_{d} are dividing each other we can use [arcsec]
+    instead. 
+
+    ---
+    TODO: Does this apply just to observations on the same Earth-Sun line?
+    If not it can be moved outside here.
+    """
+    if not rad_1au:
+        rad_1au = sun.solar_semidiameter_angular_size(date)
+    return  sun.sunearth_distance(date) * constants.au * (rad_1au / rad_d)
+
+
 class EITMap(GenericMap):
     """EIT Image Map definition"""
     
@@ -36,7 +55,7 @@ def rsun_arcseconds(self):
         return self.meta['solar_r'] * self.meta['cdelt1']
         
     def _fix_dsun(self):
-        dsun = constants.au * sun.angular_size(self.date) / (self.rsun_arcseconds)
+        dsun = _dsunAtSoho(self.date, self.rsun_arcseconds)
         self.meta['dsun_obs'] = dsun
 
     @classmethod
@@ -121,7 +140,8 @@ def _fix_dsun(self):
 #            radius = sun.angular_size(self.date)
             self.meta['dsun_obs'] = constants.au
         else:
-            self.meta['dsun_obs'] = constants.au * radius / (self.rsun_arcseconds)
+            scale = self.meta.get('cdelt1')
+            self.meta['dsun_obs'] = _dsunAtSoho(self.date, radius * scale)
         
     @classmethod
     def is_datasource_for(cls, data, header, **kwargs):
diff --git a/sunpy/sun/sun.py b/sunpy/sun/sun.py
--- a/sunpy/sun/sun.py
+++ b/sunpy/sun/sun.py
@@ -21,12 +21,12 @@
 """
 from __future__ import absolute_import
 
-import math
 import cmath
 
 import numpy as np
 
 from sunpy.time import parse_time, julian_day, julian_centuries
+from sunpy.sun import constants
 
 __all__ = ["print_params"
            ,"heliographic_solar_center"
@@ -51,7 +51,7 @@
            ,"mean_ecliptic_longitude"
            ,"eccentricity_SunEarth_orbit"
            ,"position"
-           ,"angular_size"
+           ,"solar_semidiameter_angular_size"
            ,"solar_cycle_number"]
 
 __authors__ = ["Steven Christe"]
@@ -62,15 +62,16 @@ def solar_cycle_number(t=None):
     result = (time.year + 8) % 28 + 1
     return result
 
-#def radius(t=None):
-#    return angular_size(t)
-
-def angular_size(t=None):
-    """Return the angular size of the Sun as a function of 
-    time as viewed from Earth (in arcsec)"""
-    result = 959.63 / sunearth_distance(t)
-    return result
-
+def solar_semidiameter_angular_size(t=None):
+    """Return the angular size of the semi-diameter of the Sun as 
+    a function of time as viewed from Earth (in arcsec)
+    Radius_{\sun}[rad] = \atan(\frac{<Radius_{\sun}[m]>}{D_{\sun\earth}(t)[m]})
+     since tan x ~ x when x << 1
+    Radius_{\sun}[rad] = \frac{<Radius_{\sun}[m]>)}{D_{\sun\earth}(t)[m]}
+    """
+    solar_semidiameter_rad = constants.radius / (sunearth_distance(t) * constants.au)
+    return np.rad2deg(solar_semidiameter_rad) * 60. * 60.
+ 
 def position(t=None):
     """Returns the position of the Sun (right ascension and declination)
     on the celestial sphere using the equatorial coordinate system in arcsec.
@@ -140,18 +141,19 @@ def true_anomaly(t=None):
     return result
 
 def sunearth_distance(t=None):
-    """Returns the Sun Earth distance. There are a set of higher accuracy terms not included here."""  
+    """Returns the Sun Earth distance (AU). There are a set of higher 
+    accuracy terms not included here."""  
     ta = true_anomaly(t)
     e = eccentricity_SunEarth_orbit(t)
-    result = 1.00000020 * (1.0 - e ** 2) / (1.0 + e * math.cos(np.radians(ta)))
+    result = 1.00000020 * (1.0 - e ** 2) / (1.0 + e * np.cos(np.radians(ta)))
     return result
 
 def apparent_longitude(t=None):
     """Returns the apparent longitude of the Sun."""
     T = julian_centuries(t)
-    omega = 259.18 - 1934.142*T
+    omega = 259.18 - 1934.142 * T
     true_long = true_longitude(t)        
-    result = true_long - 0.00569 - 0.00479*math.sin(np.radians(omega))
+    result = true_long - 0.00569 - 0.00479 * np.sin(np.radians(omega))
     return result
 
 def true_latitude(t=None): # pylint: disable=W0613
@@ -170,22 +172,22 @@ def true_obliquity_of_ecliptic(t=None):
 def true_rightascenscion(t=None):
     true_long = true_longitude(t)
     ob = true_obliquity_of_ecliptic(t)
-    result = math.cos(np.radians(ob))*math.sin(np.radians(true_long))
+    result = np.cos(np.radians(ob)) * np.sin(np.radians(true_long))
     return result
 
 def true_declination(t=None):
-    result = math.cos(np.radians(true_longitude(t)))
+    result = np.cos(np.radians(true_longitude(t)))
     return result
 
 def apparent_obliquity_of_ecliptic(t=None):
     omega = apparent_longitude(t)
-    result = true_obliquity_of_ecliptic(t) + 0.00256 * math.cos(np.radians(omega))
+    result = true_obliquity_of_ecliptic(t) + 0.00256 * np.cos(np.radians(omega))
     return result
 
 def apparent_rightascenscion(t=None):
     """Returns the apparent right ascenscion of the Sun."""
-    y = math.cos(np.radians(apparent_obliquity_of_ecliptic(t))) * math.sin(np.radians(apparent_longitude(t)))
-    x = math.cos(np.radians(apparent_longitude(t)))
+    y = np.cos(np.radians(apparent_obliquity_of_ecliptic(t))) * np.sin(np.radians(apparent_longitude(t)))
+    x = np.cos(np.radians(apparent_longitude(t)))
     rpol = cmath.polar(complex(x,y))
     app_ra = rpol[1] % 360.0
     if app_ra < 0: app_ra += 360.0
@@ -196,7 +198,7 @@ def apparent_declination(t=None):
     """Returns the apparent declination of the Sun."""
     ob = apparent_obliquity_of_ecliptic(t)
     app_long = apparent_longitude(t)
-    result = np.degrees(math.asin(math.sin(np.radians(ob)))*math.sin(np.radians(app_long)))
+    result = np.degrees(np.arcsin(np.sin(np.radians(ob))) * np.sin(np.radians(app_long)))
     return result
 
 def solar_north(t=None):
@@ -208,10 +210,10 @@ def solar_north(t=None):
     k = 74.3646 + 1.395833 * T
     lamda = true_longitude(t) - 0.00569
     omega = apparent_longitude(t)
-    lamda2 = lamda - 0.00479 * math.sin(np.radians(omega))
+    lamda2 = lamda - 0.00479 * np.sin(np.radians(omega))
     diff = np.radians(lamda - k)
-    x = np.degrees(math.atan(-math.cos(np.radians(lamda2)*math.tan(np.radians(ob1)))))
-    y = np.degrees(math.atan(-math.cos(diff)*math.tan(np.radians(i))))
+    x = np.degrees(np.arctan(-np.cos(np.radians(lamda2)*np.tan(np.radians(ob1)))))
+    y = np.degrees(np.arctan(-np.cos(diff) * np.tan(np.radians(i))))
     result = x + y
     return result
 
@@ -228,10 +230,10 @@ def heliographic_solar_center(t=None):
     #lamda2 = lamda - 0.00479 * math.sin(np.radians(omega))
     diff = np.radians(lamda - k)
     # Latitude at center of disk (deg):    
-    he_lat = np.degrees(math.asin(math.sin(diff)*math.sin(np.radians(i))))
+    he_lat = np.degrees(np.arcsin(np.sin(diff)*np.sin(np.radians(i))))
     # Longitude at center of disk (deg):
-    y = -math.sin(diff)*math.cos(np.radians(i))
-    x = -math.cos(diff)
+    y = -np.sin(diff)*np.cos(np.radians(i))
+    x = -np.cos(diff)
     rpol = cmath.polar(complex(x,y))
     he_lon = np.degrees(rpol[1]) - theta
     he_lon = he_lon % 360
@@ -246,7 +248,7 @@ def print_params(t=None):
     print('Solar Ephemeris for ' + time.ctime())
     print('')
     print('Distance (AU) = ' + str(sunearth_distance(t)))
-    print('Semidiameter (arc sec) = ' + str(angular_size(t)))
+    print('Semidiameter (arc sec) = ' + str(solar_semidiameter_angular_size(t)))
     print('True (long,lat) in degrees = (' + str(true_longitude(t)) + ',' 
                                                  + str(true_latitude(t)) + ')')
     print('Apparent (long, lat) in degrees = (' + str(apparent_longitude(t)) + ',' 


Test patch: