;+
;$Id: scc_triangulate.pro,v 1.3 2009/11/19 16:08:35 thompson Exp $
;
; Project     :	STEREO - SECCHI
;
; Name        :	SCC_TRIANGULATE()
;
; Purpose     :	Triangulate 3D coordinates from STEREO measurements
;
; Category    :	STEREO, SECCHI, Coordinates
;
; Explanation :	The (helioprojective-cartesian) angular coordinates of a given
;               solar feature, as seen by the STEREO Ahead and Behind
;               spacecraft, are intercompared to derive the three-dimensional
;               position.
;
;               The accuracy of this procedure depends critically on choosing
;               angular coordinates which represent the same point in space.
;               If the chosen lines of sight from the two spacecraft do not
;               intersect, that will introduce an error.  The optional
;               parameter GAP returns the distance of closest approach between
;               the two lines of sight.
;
; Syntax      :	Coord = SCC_TRIANGULATE(DATE, HPC_AHEAD, HPC_BEHIND  [, GAP ])
;
; Inputs      :	DATE       = The date and time of the data, in any format
;                            recognized by ANYTIM2UTC.  Can either be a scalar
;                            or an array consistent with HPC_AHEAD and
;                            HPC_BEHIND.
;
;               HPC_AHEAD  = Two-element array containing the
;                            helioprojective-cartesian longitude (X) and
;                            latitude (Y) for the Ahead spacecraft.  Can also
;                            have additional dimensions, e.g. [2,n1,n2,...].
;
;               HPC_BEHIND = Same for the Behind spacecraft.
;
; Opt. Inputs :	None.
;
; Outputs     :	The result of the function is an array containing the derived
;               3D coordinates in (x,y,z) format.  The array will have the
;               dimensions [3,n1,n2,...] corresponding to the dimensions of
;               HPC_AHEAD and HPC_BEHIND.
;
; Opt. Outputs:	GAP    = Closest distance between the lines of sight.
;
; Keywords    :	SYSTEM = Character string, giving one of the following
;                        standard coordinate systems:
;
;                               HCI     Heliocentric Inertial (default)
;                               HAE     Heliocentric Aries Ecliptic
;                               HEE     Heliocentric Earth Ecliptic
;                               HEEQ    Heliocentric Earth Equatorial (or HEQ)
;                               Carrington (can be abbreviated)
;
;                        The default is HEEQ.
;
;               CUNIT  = String containing the units of HPC_AHEAD and
;                        HPC_BEHIND.  Valid values are "deg", "arcmin",
;                        "arcsec", "mas" (milliarcseconds), and "rad"
;                        (radians).  The default is "arcsec".
;
;               METERS = If set, then the coordinates are returned in units of
;                        meters, instead of the default of kilometers.  Note
;                        that meters are required for FITS header keywords.
;
;               AU     = If set, then the coordinates are returned in
;                        Astronomical Units, instead of the default of
;                        kilometers.
;
;               SOLRAD = If set, then the coordinates are returned in solar
;                        radii, instead of the default of kilometers.
;
;               SPACECRAFT = Two-element string array giving the names of the
;                        spacecraft associated with data set.  The default is
;                        SPACECRAFT=['Ahead','Behind'], but one also substitute
;                        'Earth' or 'SOHO' for either of these.  The use of
;                        'SOHO' also requires that the SOHO ephemerides be
;                        loaded via LOAD_SOHO_SPICE.
;
; Calls       :	ANYTIM2UTC, GET_STEREO_COORD, CONVERT_STEREO_COORD
;
; Common      :	None.
;
; Restrictions:	Currently only works for STEREO Ahead and Behind images.  Does
;               not (yet) work for combinations of STEREO and other
;               observatories (e.g. SOHO).
;
; Side effects:	None.
;
; Prev. Hist. :	None.
;
; Written     : 22-Sep-2006, William Thompson, GSFC
;
; $Log: scc_triangulate.pro,v $
; Revision 1.3  2009/11/19 16:08:35  thompson
; Make loop variable long.
;
; Revision 1.2  2008-10-31 15:47:09  thompson
; Include support for Earth-based observatories, and SOHOd
;
; Revision 1.1  2006/09/27 17:04:24  nathan
; moved from dev/analysis
;
; Revision 1.2  2006/09/26 21:43:10  nathan
; add CVS keywords
;
; Contact     :	WTHOMPSON
;
;-
;
function scc_triangulate, date, hpc_ahead, hpc_behind, gap, cunit=k_cunit, $
                          system=k_system, spacecraft=spacecraft, $
                          meters=meters, au=au, solrad=solrad
on_error, 2
;
if n_elements(k_system) eq 1 then system=k_system else system='HEEQ'
if n_elements(spacecraft) eq 2 then sc=spacecraft else sc=['ahead','behind']
;
;  Check the input parameters.  Make sure that they have the right
;  dimensionality.  Convert DATE to UTC.
;
if n_params() lt 3 then message, $
  'Syntax:  Result = SCC_TRIANGULATE(DATE, HPC_AHEAD, HPC_BEHIND)'
;
if n_elements(date) eq 0 then message, 'DATE not defined'
message = ''
utc = anytim2utc(date, errmsg=message)
if message ne '' then message, message
n_date = n_elements(utc)
if n_date gt 1 then begin
    sz_date  = size(utc)
    dim_date = sz_date[1:sz_date[0]]
endif
;
sz_ahead = size(hpc_ahead)
if sz_ahead[0] eq 0 then message, 'HPC_AHEAD must be an array'
if sz_ahead[1] ne 2 then message, 'First dimension of HPC_AHEAD must be 2'
n_ahead = n_elements(hpc_ahead) / 2
if n_ahead gt 1 then dim_ahead = sz_ahead[2:sz_ahead[0]]
;
sz_behind = size(hpc_behind)
if sz_behind[0] eq 0 then message, 'HPC_BEHIND must be an array'
if sz_behind[1] ne 2 then message, 'First dimension of HPC_BEHIND must be 2'
n_behind = n_elements(hpc_behind) / 2
if n_behind gt 1 then dim_behind = sz_behind[2:sz_behind[0]]
;
;  Make sure that the dimensions of the arrays agree.
;
if n_ahead ne n_behind then message, $
  'Arrays HPC_AHEAD and HPC_BEHIND do not agree'
if n_ahead gt 1 then begin
    if n_elements(dim_ahead) ne n_elements(dim_behind) then message, $
      'Arrays HPC_AHEAD and HPC_BEHIND do not agree'
    w = where(dim_ahead ne dim_behind, count)
    if count gt 0 then message, $
      'Arrays HPC_AHEAD and HPC_BEHIND do not agree'
endif
if n_date gt 1 then begin
    if n_date ne n_ahead then message, $
      'Arrays DATE and HPC_AHEAD do not agree'
    if n_date ne n_behind then message, $
      'Arrays DATE and HPC_BEHIND do not agree'
    w = where(dim_date ne dim_ahead, count)
    if count gt 0 then message, $
      'Arrays DATE and HPC_AHEAD do not agree'
    w = where(dim_date ne dim_behind, count)
    if count gt 0 then message, $
      'Arrays DATE and HPC_BEHIND do not agree'
endif
;
;  Parse the CUNIT string, if passed.
;
if n_elements(k_cunit) eq 1 then cunit=strlowcase(k_cunit) else cunit='arcsec'
if strmid(cunit,0,3) eq 'deg'    then cunit = 'deg'
if strmid(cunit,0,6) eq 'arcmin' then cunit = 'arcmin'
if strmid(cunit,0,6) eq 'arcsec' then cunit = 'arcsec'
if strmid(cunit,0,3) eq 'rad'    then cunit = 'rad'
;
;  Convert the coordinates into radians.
;
conv = !dpi / 180.d0
case cunit of
    'arcmin': conv = conv / 60.d0
    'arcsec': conv = conv / 3600.d0
    'mas':    conv = conv / 3600.d3
    'rad':    conv = 1.d0
    else:     conv = conv
endcase
ahead  = conv * hpc_ahead
behind = conv * hpc_behind
;
;  Simplify the dimensions.
;
if n_date gt 1 then utc = reform(utc, n_date, /overwrite)
if n_ahead gt 1 then begin
    ahead  = reform(ahead,  2, n_ahead, /overwrite)
    behind = reform(behind, 2, n_ahead, /overwrite)
endif
;
;  Get the values of Dsun, in kilometers.
;
dsun_ahead  = get_stereo_coord(utc, 'ahead',  system='HCI')
dsun_behind = get_stereo_coord(utc, 'behind', system='HCI')
dsun_ahead  = sqrt(total(dsun_ahead^2,  1))
dsun_behind = sqrt(total(dsun_behind^2, 1))
;
;  Calculate the equivalent heliocentric coordinates for distances D within
;  +/- 100 Mm of Dsun.  This defines a line which can be extrapolated.
;
lon = reform(ahead[0,*])
lat = reform(ahead[1,*])
cosy = cos(lat)
dx = cosy * cos(lon)
dy = cosy * sin(lon)
dz = sin(lat)
d = dsun_ahead - 1d6
coord_ahead1 = reform(dblarr(3,n_ahead))
coord_ahead1[0,*] = dsun_ahead - d * dx
coord_ahead1[1,*] = d * dy
coord_ahead1[2,*] = d * dz
d = dsun_ahead + 1d6
coord_ahead2 = reform(dblarr(3,n_ahead))
coord_ahead2[0,*] = dsun_ahead - d * dx
coord_ahead2[1,*] = d * dy
coord_ahead2[2,*] = d * dz
;
lon = reform(behind[0,*])
lat = reform(behind[1,*])
cosy = cos(lat)
dx = cosy * cos(lon)
dy = cosy * sin(lon)
dz = sin(lat)
d = dsun_behind - 1d6
coord_behind1 = reform(dblarr(3,n_behind))
coord_behind1[0,*] = dsun_behind - d * dx
coord_behind1[1,*] = d * dy
coord_behind1[2,*] = d * dz
d = dsun_behind + 1d6
coord_behind2 = reform(dblarr(3,n_behind))
coord_behind2[0,*] = dsun_behind - d * dx
coord_behind2[1,*] = d * dy
coord_behind2[2,*] = d * dz
;
;  Convert into the requested coordinate system.
;
convert_stereo_coord, utc, coord_ahead1,  'HGRTN', system, spacecraft=sc[0]
convert_stereo_coord, utc, coord_ahead2,  'HGRTN', system, spacecraft=sc[0]
convert_stereo_coord, utc, coord_behind1, 'HGRTN', system, spacecraft=sc[1]
convert_stereo_coord, utc, coord_behind2, 'HGRTN', system, spacecraft=sc[1]
;
;  Find the points of closest approach of the two lines.
;
coord_ahead  = coord_ahead1
coord_behind = coord_behind1
for i=0L,n_ahead-1 do begin
    x1a = coord_ahead1 [0,i]  &  x2a = coord_ahead2 [0,i]
    y1a = coord_ahead1 [1,i]  &  y2a = coord_ahead2 [1,i]
    z1a = coord_ahead1 [2,i]  &  z2a = coord_ahead2 [2,i]
;
    x1b = coord_behind1[0,i]  &  x2b = coord_behind2[0,i]
    y1b = coord_behind1[1,i]  &  y2b = coord_behind2[1,i]
    z1b = coord_behind1[2,i]  &  z2b = coord_behind2[2,i]
;
    pya0 = (y1a*x2a-y2a*x1a) / (x2a-x1a)  &  pya1 = (y2a-y1a) / (x2a-x1a)
    pza0 = (z1a*x2a-z2a*x1a) / (x2a-x1a)  &  pza1 = (z2a-z1a) / (x2a-x1a)
    pyb0 = (y1b*x2b-y2b*x1b) / (x2b-x1b)  &  pyb1 = (y2b-y1b) / (x2b-x1b)
    pzb0 = (z1b*x2b-z2b*x1b) / (x2b-x1b)  &  pzb1 = (z2b-z1b) / (x2b-x1b)
;
    alpha = 1 + pya1*pyb1 + pza1*pzb1
    mat = [[1 + pya1^2 + pza1^2, -alpha], [alpha, -(1+pyb1^2+pzb1^2)]]
    vec = [pya1*(pyb0-pya0) + pza1*(pzb0-pza0), $
           pyb1*(pyb0-pya0) + pzb1*(pzb0-pza0)]
    xx = invert(mat) ## vec
    coord_ahead[0,i]  = xx[0]
    coord_ahead[1,i]  = pya0 + pya1*xx[0]
    coord_ahead[2,i]  = pza0 + pza1*xx[0]
    coord_behind[0,i] = xx[1]
    coord_behind[1,i] = pyb0 + pyb1*xx[1]
    coord_behind[2,i] = pzb0 + pzb1*xx[1]
endfor
;
;  Calculate the result as the point midway between the points of closest
;  approach.  Also calculate the distance between the points.
;
coord = (coord_ahead + coord_behind) / 2
gap = sqrt(total((coord_ahead-coord_behind)^2,1))
;
;  Convert to the requested coordinate system.
;
conv = 1
if keyword_set(meters) then conv = 1d-3
if keyword_set(au) then conv = 1.4959787d8
if keyword_set(solrad) then conv = 6.95508d5
if conv ne 1 then begin
    coord = temporary(coord) / conv
    gap   = temporary(gap)   / conv
endif
;
;  Reformat to the original dimensions.
;
if n_ahead gt 1 then begin
    coord = reform(coord, [3,dim_ahead], /overwrite)
    gap   = reform(gap, dim_ahead, /overwrite)
endif
;
return, coord
end
