subroutine gwcs_observatory_internal(arg,lonlat,altitude,slimit,diam,found)
  use gwcs_interfaces, except_this=>gwcs_observatory_internal
  !---------------------------------------------------------------------
  ! @ private
  !  Internal subroutine, do not call directly! Use generic API
  ! 'gwcs_observatory' to retrieve a known observatory parameters, or
  ! use 'gwcs_observatory_exists' to check if it is known here.
  ! ---
  !  Return the observatory parameters given its name, with a flag
  ! 'found' if it was found or not.
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: arg        ! Telescope name
  real(kind=8),     intent(inout) :: lonlat(2)  !
  real(kind=8),     intent(inout) :: altitude   !
  real(kind=8),     intent(inout) :: slimit     !
  real(kind=4),     intent(inout) :: diam       !
  logical,          intent(out)   :: found      !
  ! Local
  character(len=4) :: teles  ! Comparison is based on 4 characters
                             ! Hide this "detail" here
  logical :: error
  !
  teles = arg  ! arg can be smaller or larger than 4 characters!
  !
  lonlat(:) = 0.d0
  altitude = 0.d0
  slimit = 0.d0
  diam  = 0.
  found = .true.
  error = .false.
  !
  select case (teles)
  !
  ! IRAM instruments
  case ('BURE','PDBI','NOEM')
    ! Interferometer array center S.Guilloteau 06-SEP-1989
    call gwcs_sexa ('05:54:28.5',lonlat(1),error)
    call gwcs_sexa ('44:38:02.0',lonlat(2),error)
    altitude = 2.560d0  ! Elevation axis of antennas...
    slimit = 32.d0 ! was 45 deg. until Jul 2009, then 35 until 12 Nov 2014.
    diam = 15
  case ('PICO','30M ','VELE')
! From IRAM Website - Wikipedia is incorrect
    call gwcs_sexa ('-03:23:55.51',lonlat(1),error)
    call gwcs_sexa ('37:04:06.29',lonlat(2),error)
! Before 1-Mar-2016
!    call gwcs_sexa ('-03:23:58.1',11,lonlat(1),error)
!    call gwcs_sexa ('37:04:05.6',10,lonlat(2),error)
    altitude = 2.850d0
    slimit = 1.d0  ! was 2 deg. until Oct 2009
    diam = 30
  !
  ! submm/mm instruments
  case ('ALMA')
    call gwcs_sexa ('-67:45:11.6',lonlat(1),error)
    call gwcs_sexa ('-23:01:11.7',lonlat(2),error)
    altitude = 5.0d0
    slimit = 0.d0                 ! Sun can be observed ...
    diam = 12
  case ('ACA ')
    call gwcs_sexa ('-67:45:11.6',lonlat(1),error)
    call gwcs_sexa ('-23:01:11.7',lonlat(2),error)
    altitude = 5.0d0
    slimit = 0.d0                 ! Sun can be observed ...
    diam = 7
  case ('APEX')
    call gwcs_sexa ('-67:45:32.9',lonlat(1),error)   ! -67 45 32.90350
    call gwcs_sexa ('-23:00:20.8',lonlat(2),error)   !  23 00 20.80366
    altitude = 5.104d0                              ! 5104.4706
    slimit = 30.d0                ! From D.Muders 19.05.2011
    diam = 12
  case ('ATF ')
    call gwcs_sexa ('-107:37:10.02',lonlat(1),error)
    call gwcs_sexa ('34:04:29.8',lonlat(2),error)
    altitude = 2.13542d0
    slimit = 0.d0
    diam = 12
  case ('CARM')
    ! Rough values -118.1 37.3
    call gwcs_sexa ('-118:06:00.00',lonlat(1),error)
    call gwcs_sexa ('37:33:00.0',lonlat(2),error)
    altitude = 2.200d0
    slimit = 30.d0                ! ?
    diam = 10.4
  case ('CSO ')
    ! Values from University Hawaii web-site
    call gwcs_sexa ('-155:28:31.78954',lonlat(1),error)
    call gwcs_sexa ('19:49:20.77658',lonlat(2),error)
    altitude = 4.200d0
    slimit = 30.d0                ! ?
    diam = 10.4
  case ('FCRA')
    ! Rough values
    call gwcs_sexa ('-72:21:00.0',lonlat(1),error)
    call gwcs_sexa ('42:23:24.0',lonlat(2),error)
    altitude = 0.0d0             ! guess
    slimit = 0.d0
    diam = 15
  case ('GLT ')  ! GreenLand Telescope
    call gwcs_sexa ('-68:41:06.0',lonlat(1),error)
    call gwcs_sexa ('76:32:06.0',lonlat(2),error)
    altitude = 0.1d0
    slimit = 0.d0
    diam = 12
  case ('HAYS')  ! MIT Haystack
    call gwcs_sexa ('-71:29:18.636',lonlat(1),error)
    call gwcs_sexa ('42:37:22.944',lonlat(2),error)
    altitude = 0.125d0
    slimit = 0.d0
    diam = 36.6
  case ('JCMT')
    ! Values from University Hawaii web-site
    call gwcs_sexa ('-155:28:37.20394',lonlat(1),error)
    call gwcs_sexa ('19:49:22.10741',lonlat(2),error)
    altitude = 4.111d0
    slimit = 30.d0
    diam = 15
  case ('KITT')
    ! Values from email exchange with Remo Tilanus in Sept. 2025
    call gwcs_sexa ('-111:36:53.475',lonlat(1),error)
    call gwcs_sexa ('31:57:12.000',lonlat(2),error)
    altitude = 1.914d0
    slimit = 30.d0
    diam = 12
  case ('KOSM')
    call gwcs_sexa ('07:47:02.4',lonlat(1),error)
    call gwcs_sexa ('45:59:02.4',lonlat(2),error)
    altitude = 3.089d0
    slimit = 30.d0
    diam = 3.0
  case ('LMT')
    ! Rough values from website
    call gwcs_sexa ('-97:18:53',lonlat(1),error)
    call gwcs_sexa ('18:59:06',lonlat(2),error)
    altitude = 4.600d0
    slimit = 0.d0  ! Unknown
    diam = 50.0
  case ('MRO ')  ! Metsähovi Radio Observatory
    call gwcs_sexa ('24:23:35.19791',lonlat(1),error)
    call gwcs_sexa ('60:13:04.11295',lonlat(2),error)
    altitude = 0.0603d0
    slimit = 0.d0
    diam = 13.7
  case ('NOBE','NRO ')
    call gwcs_sexa ('138:28:33.0',lonlat(1),error)
    call gwcs_sexa ('35:56:30.0',lonlat(2),error)
    altitude = 1.350d0
    slimit = 30.d0
    diam = 10.0
  case ('NOTO')  ! Noto Radio Observatory
    call gwcs_sexa ('14:59:20.22',lonlat(1),error)
    call gwcs_sexa ('36:52:34.86',lonlat(2),error)
    altitude = 0.032d0
    slimit = 0.d0
    diam = 32.0
  case ('OSO')  ! Onsala Space Observatory 20m
    call gwcs_sexa ('11:55:34.8719',lonlat(1),error)
    call gwcs_sexa ('57:23:45.0046',lonlat(2),error)
    altitude = 0.022758d0
    slimit = 0.d0  ! Radome
    diam = 20.0
  case ('SEST')
    ! Rough value ?
    call gwcs_sexa ('-70:43:48',lonlat(1),error)
    call gwcs_sexa ('-29:15:24',lonlat(2),error)
    altitude = 2.347d0
    slimit = 60.d0
    diam = 15.0
  case ('SMA ')
    ! Array reference position - M. Gurwell communication - Sep 2019 
    call gwcs_sexa ('-155:28:39.08',lonlat(1),error)
    call gwcs_sexa ('19:49:27.14',lonlat(2),error)
    altitude = 4.108d0  ! radius: 6382.248km
    slimit = 25.d0
    diam = 6.0
  case ('SMT ','HHT ')
    ! GPS result from VLBI according to Dirk Muders.
    call gwcs_sexa ('-109:53:28.48',lonlat(1),error)
    call gwcs_sexa ('32:42:05.8',lonlat(2),error)
    altitude = 3.1860d0
    slimit = 30.d0
    diam = 10.0
  case ('SPT ')
    ! From J.E.Carlstrom et al. 2011
    ! Publications of the Astronomical Society of the Pacific, Vol. 123, No. 903 (May 2011), pp. 568-581
    call gwcs_sexa ('00:00:00',lonlat(1),error)
    call gwcs_sexa ('-90:00:00',lonlat(2),error)
    altitude = 2.800d0
    slimit = 0.d0
    diam = 10.0
  case ('SRT ')  ! Sardinia Radio Telescope
    call gwcs_sexa ('9:14:42.5764',lonlat(1),error)
    call gwcs_sexa ('39:29:34.93742',lonlat(2),error)
    altitude = 0.6d0
    slimit = 0.d0  ! Unknown
    diam = 64.0
  case ('TRAO')  ! Taeduk Radio Astronomy Observatory (South Korea)
    call gwcs_sexa ('127:22:30.591',lonlat(1),error)
    call gwcs_sexa ('36:23:51.162',lonlat(2),error)
    altitude = 0.144d0
    slimit = 0.d0  ! Under a radome
    diam = 13.716
  case ('WEST')  ! MIT Westford
    call gwcs_sexa ('-71:29:38.328',lonlat(1),error)
    call gwcs_sexa ('42:36:46.512',lonlat(2),error)
    altitude = 0.100d0
    slimit = 0.d0
    diam = 18.3
  case ('YEBE')
    call gwcs_sexa ('-3:05:12.71',lonlat(1),error)
    call gwcs_sexa ('40:31:28.78',lonlat(2),error)
    altitude = 0.99d0
    slimit = 2.d0
    diam = 40.0
  !
  ! cm instruments
  case ('EFFE')
    call gwcs_sexa ('06:53:02',lonlat(1),error)
    call gwcs_sexa ('50:31:29',lonlat(2),error)
    altitude = 0.360d0
    slimit = 20.d0
    diam = 100.0
  case ('GBT')
    call gwcs_sexa ('-79:50:23.406',lonlat(1),error)
    call gwcs_sexa ('38:25:59.236',lonlat(2),error)
    altitude = 0.80743d0
    slimit = 0.d0
    diam = 100.0
  case ('VLA ','EVLA','JVLA')
    call gwcs_sexa ('-107:37:06',lonlat(1),error)
    call gwcs_sexa ('34:04:42',lonlat(2),error)
    altitude = 2.124d0
    slimit = 0.d0
    diam = 25.0
  case ('MEDI')  ! Medicina Radio Observatory
    call gwcs_sexa ('11:38:48.9',lonlat(1),error)
    call gwcs_sexa ('44:31:13.8',lonlat(2),error)
    altitude = 0.025d0
    slimit = 0.d0
    diam = 32.0
  !
  ! other instruments
  case ('LASI')
    call gwcs_sexa ('-70:43:48',lonlat(1),error)
    call gwcs_sexa ('-29:15:24',lonlat(2),error)
    altitude = 2.347d0
    slimit = 60.d0
  case ('MAUN')
    call gwcs_sexa ('-155:28:18',lonlat(1),error)
    call gwcs_sexa ('19:46:36',lonlat(2),error)
    altitude = 4.200d0
    slimit = 60.d0
  case ('PARA','VLT ','VLTI')
    ! Exact value taken from the Officials Documents as of 10/14/97 GD
    call gwcs_sexa ('-70:24:11.642',lonlat(1),error)
    call gwcs_sexa ('-24:37:33.117',lonlat(2),error)
    altitude = 2.636d0
    slimit = 60.d0
  case ('IOTA')
    call gwcs_sexa ('-110:53:05',lonlat(1),error)
    call gwcs_sexa ('31:14:11',lonlat(2),error)
    altitude = 2.608d0
    slimit = 60.d0
  case ('PTI ')
    call gwcs_sexa ('-116:51:47.88',lonlat(1),error)
    call gwcs_sexa ('33:21:24.12',lonlat(2),error)
    altitude = 1.687d0
    slimit = 60.d0
  case ('GI2T')
    call gwcs_sexa ('06:55:36',lonlat(1),error)
    call gwcs_sexa ('43:44:56',lonlat(2),error)
    altitude = 1.270d0
  case ('FAST')
    call gwcs_sexa ('106:51:24.0',lonlat(1),error)
    call gwcs_sexa ('25:39:10.6',lonlat(2),error)
    altitude = 1.1100288
    slimit = 5.d0
    diam = 300.d0  ! Illuminated diameter 300m over 500m
  case default
    found = .false.
  end select
  !
end subroutine gwcs_observatory_internal
!
function gwcs_observatory_exists(arg)
  !---------------------------------------------------------------------
  ! @ public
  ! Function evaluates as true if the named observatory is known here
  !---------------------------------------------------------------------
  logical :: gwcs_observatory_exists
  character(len=*), intent(in) :: arg ! Telescope name
  ! Local
  real(kind=8) :: lonlat(2),altitude,slimit
  real(kind=4) :: diam
  !
  ! Retrieving all the parameters is useless but this avoids duplicating
  ! the list of supported observatories
  call gwcs_observatory_internal(arg,lonlat,altitude,slimit,diam,gwcs_observatory_exists)
  !
end function gwcs_observatory_exists
!
subroutine gwcs_observatory_parameters(arg,lonlat,altitude,slimit,diam,error)
  use gbl_message
  use gwcs_interfaces, except_this=>gwcs_observatory_parameters
  !---------------------------------------------------------------------
  ! @ public-generic gwcs_observatory
  !  Return the observatory parameters given its name, raise an error
  ! if not found
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: arg        ! Telescope name
  real(kind=8),     intent(inout) :: lonlat(2)  !
  real(kind=8),     intent(inout) :: altitude   !
  real(kind=8),     intent(inout) :: slimit     !
  real(kind=4),     intent(inout) :: diam       !
  logical,          intent(inout) :: error      ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='OBSERVATORY'
  logical :: found
  !
  call gwcs_observatory_internal(arg,lonlat,altitude,slimit,diam,found)
  if (.not.found) then
    call gwcs_message(seve%e,rname,'Unknown telescope name '//arg)
    error = .true.
    return
  endif
  !
end subroutine gwcs_observatory_parameters
!
subroutine gwcs_observatory_telesco(arg,telescope,error)
  use gbl_message
  use image_def
  use gwcs_interfaces, except_this=>gwcs_observatory_telesco
  !---------------------------------------------------------------------
  ! @ public-generic gwcs_observatory
  !  Return the observatory parameters in the 'telesco' structure given
  ! its name.
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: arg        ! Telescope name
  type(telesco),    intent(out)   :: telescope  ! Telescope structure
  logical,          intent(inout) :: error      ! Logical error flag
  ! Local
  ! real(kind=8) :: xyz(3)
  real(kind=8) :: lonlat(2)
  real(kind=8) :: altitude
  real(kind=8) :: slimit
  real(kind=4) :: diam
  !
  call gwcs_observatory_parameters(arg,lonlat,altitude,slimit,diam,error)
  if (error)  return
  !
  telescope%ctele = arg
  telescope%lon = lonlat(1)
  telescope%lat = lonlat(2)
  telescope%alt = altitude*1d3
  telescope%diam = diam
  !
  ! Debugging:
  ! call gwcs_lonlat2geo(lonlat(1),lonlat(2),altitude,xyz)
  ! print *,'Telescope ',telescope%ctele
  ! print *,'Lon Lat ',lonlat,' Alt ',telescope%alt
  ! print *,'XYZ ',xyz
end subroutine gwcs_observatory_telesco
!
subroutine gwcs_print_telescope(teles,line)
  use image_def
  use gwcs_dependencies_interfaces
  use gwcs_interfaces, except_this=>gwcs_print_telescope
  !---------------------------------------------------------------------
  ! @ public
  ! Build a one-line description of a type 'telesco'. This line is well
  ! suited for command V\HEADER.
  !---------------------------------------------------------------------
  type(telesco),    intent(in)  :: teles
  character(len=*), intent(out) :: line
  ! Local
  character(len=13) :: chlon,chlat
  !
  call deg2sexa(teles%lon,360,chlon,2)
  call deg2sexa(teles%lat,360,chlat,1)
  line = 'Tel: '//teles%ctele//chlon//chlat//' Alt.          Diam '
  write(line(52:58),'(F6.1)') teles%alt
  write(line(65:71),'(F5.1)') teles%diam
  !
end subroutine gwcs_print_telescope
!
subroutine gwcs_sexa(line,value,error)
  use gbl_message
  use gwcs_interfaces, except_this=>gwcs_sexa
  !---------------------------------------------------------------------
  ! @ public
  !  Decode a sexagesimal notation
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: line   !
  real(kind=8),     intent(out)   :: value  !
  logical,          intent(inout) :: error  !
  ! Local
  character(len=*), parameter :: rname='SEXA'
  real(8) :: value1,sign
  integer :: nf,nl,nc, nline
  character(len=64) :: chain
  integer :: ier
  !
  ! First, get the real string to be decoded:
  nline = len_trim(line)
  nl = index(line(1:nline),':')
  if (nl.eq.0) then
    read(line(1:nline),*,iostat=ier) value
    error = ier.ne.0
    return
  else
    chain = line(1:nline)
    nc = nline
  endif
  !
  ! Then, start decoding
  if (nl.eq.1 .or. nl.eq.nc) then
    call gwcs_message(seve%e,rname,'Syntax error')
    error = .true.
    return
  endif
  !
  ! Decode HOURS/DEGREES
  read(chain(1:nl-1),*,iostat=ier) value
  error = ier.ne.0
  if (error) return
  if (value.gt.0d0) then
    sign = 1.0d0
  elseif (value.lt.0d0) then
    sign = -1.0d0
    value = -value
  else
    if (chain(1:1).eq.'-') then
      sign = -1.0d0
    else
      sign = 1.0d0
    endif
  endif
  !
  ! Decode MINUTES
  nf = nl+1
  nl = index(chain(nf:nc),':')
  if (nl.eq.1 .or. nl+nf-1.eq.nc) then
    call gwcs_message(seve%e,rname,'Syntax error')
    error = .true.
    return
  endif
  if (nl.eq.0) then
    read(chain(nf:nc),*,iostat=ier) value1
    error = ier.ne.0
    if (error) return
  else
    read(chain(nf:nf+nl-2),*,iostat=ier) value1
  endif
  error = ier.ne.0
  if (error) return
  if (value1.lt.0.d0 .or. value1.ge.60.d0) then
    call gwcs_message(seve%e,rname,'Invalid minute field in '//chain)
    error = .true.
    return
  endif
  value = value + value1/60.d0
  if (nl.eq.0) then
    ! No SECONDS field: return now
    value = sign * value
    return
  endif
  !
  ! Decode SECONDS
  nf = nf+nl
  read(chain(nf:nc),*,iostat=ier) value1
  error = ier.ne.0
  if (error) return
  if (value1.lt.0.d0 .or. value1.ge.60.d0) then
    call gwcs_message(seve%e,rname,'Invalid second field in '//chain)
    error = .true.
    return
  endif
  value = sign * (value + value1/3600.d0)
  !
end subroutine gwcs_sexa
!
subroutine gwcs_geo2lonlat(xyz,clon,clat,alt)
  use gwcs_interfaces, except_this=>gwcs_geo2lonlat
  !----------------------------------------------------
  ! @ public
  !   GWCS
  !   Convert XYZ to Longitude, Latitude, Altitude
  !----------------------------------------------------
  real(8), intent(in) :: xyz(3)
  character(len=*), intent(out) :: clon  ! In degree, sexagesimal
  character(len=*), intent(out) :: clat  ! In degree, sexagesimal
  real(8), intent(out) :: alt

  !ASTRO ! real(8), parameter :: a_earth =6367435d0      ! earth radius in meters
  real(8), parameter :: bovera=0.99664719d+00        ! earth b/a
  real(8), parameter :: a_earth=6378140d+00          ! (m) earth radius
  real(8), parameter :: flatten=1.d0-bovera          ! Earth flattening
  real(8), parameter :: pi=3.14159265358979323846d0
  !
  real(8) :: plh(3)
  !
  call gwcs_xyz2lla(xyz,plh, a_earth, flatten)
  if (plh(1).gt.pi) plh(1) = plh(1)-2.d0*pi
  call sexag(clon,plh(1),360)
  call sexag(clat,plh(2),360)
  alt = plh(3)
  !
end subroutine gwcs_geo2lonlat
!
subroutine gwcs_lonlat2geo(clon,clat,alt,xyz)
  use gwcs_interfaces, except_this=>gwcs_lonlat2geo
  !----------------------------------------------------
  ! @ public
  !
  !----------------------------------------------------
  real(8), intent(out) :: xyz(3)
  real(8), intent(in) :: clon  ! Degree
  real(8), intent(in) :: clat  ! Degree
  real(8), intent(in) :: alt   ! km

  !ASTRO ! real(8), parameter :: a_earth =6367435d0      ! earth radius in meters
  real(8), parameter :: bovera=0.99664719d+00        ! earth b/a
  real(8), parameter :: a_earth=6378140d+00          ! (m) earth radius
  real(8), parameter :: flatten=1.d0-bovera          ! Earth flattening
  real(8), parameter :: pi=3.14159265358979323846d0
  !
  real(8) :: plh(3)
  !
  plh(1) = clon*pi/180d0
  if (plh(1).lt.0) plh(1) = plh(1)+2.d0*pi
  plh(2) = clat*pi/180d0
  plh(3) = alt*1d3
  call gwcs_lla2xyz (plh, xyz, a_earth, flatten)
  !
end subroutine gwcs_lonlat2geo
!
subroutine gwcs_xyz2lla(xyz, plh, a, fl)
  !------------------------------------------------------------
  ! @ public
  !
  ! Convert XYZ to Longitude, Latitude, Altitude
  ! for a given ellipsoid specified by A and FL
  !
  ! Longitude is East of Greenwich
  !
  ! References:
  ! Borkowski, K. M. (1989).  "Accurate algorithms to transform geocentric
  ! to geodetic coordinates", *Bulletin Geodesique*, v. 63, pp. 50-56.
  ! Borkowski, K. M. (1987).  "Transformation of geocentric to geodetic
  ! coordinates without approximations", *Astrophysics and Space Science*,
  ! v. 139, n. 1, pp. 1-4.  Correction in (1988), v. 146, n. 1, p. 201.
  !
  ! An equivalent formulation is recommended in the IERS Standards
  ! (1995), draft.
  !-------------------------------------------------------------------------
  real(8), intent(in) :: xyz(3)  ! XYZ coordinates (in units of distance)
  real(8), intent(out) :: plh(3) ! Longitude, Latitude (in radians) and height (in unit of distance)
  real(8), intent(in) :: a       ! semi-major axis of ellipsoid (in units are of distance)
  real(8), intent(in) :: fl      ! flattening of ellipsoid [unitless]
  !
  ! Global
  real(8), parameter :: pi=3.14159265358979323846d0
  !
  ! Local
  real(8) :: B ! Semi-minor axis of ellipsoid
  real(8) :: x,y,z
  real(8) :: r,e,f
  real(8) :: p,q,d
  real(8) :: g,t,v
  real(8) :: lon, lat, alt
  !
  x = xyz(1)
  y = xyz(2)
  z = xyz(3)
  !
  ! Compute semi-minor axis with sign
  B = A * (1.d0 - FL)
  if (z.lt.0.0d0) B = -B
  !
  ! Searching for latitude
  r = sqrt( x*x + y*y )
  e = ( B*z - (A*A - B*B) ) / ( A*r )
  f = ( B*z + (A*A - B*B) ) / ( A*r )
  !
  ! Solution to
  !     t^4 + 2*E*t^3 + 2*F*t - 1 = 0
  p = (4.d0 / 3.d0) * (e*f + 1.d0)
  q = 2.d0 * (e*e - f*f)
  d = p*p*p + q*q
  if (d.ge.0.d0) then
    v = (sqrt(d)-q)**(1.d0/3.d0) - (sqrt(d)+q)**(1.d0/3.d0)
  else
    v = 2.0 * sqrt(-p) * cos(acos(q/(p*sqrt(-p)))/3.d0)
  endif
  !
  ! Improve v near poles
  if ( v*v.lt.abs(p)) then
    v = -(v*v*v + 2.d0*q) / (3.d0*p)
  endif
  g = (sqrt(e*e + v ) + e) / 2.d0
  t = sqrt( g*g  + (f - v*g)/(2.d0*g - e) ) - g
  !
  ! Here is the latitude, the most complex issue
  lat = atan( (A*(1.d0 - t*t)) / (2.d0*B*t) )
  !
  ! The comes height
  alt = (r - A*t)*cos(lat) + (z - B)*sin(lat)
  !
  ! And finally, the obvious, the longitude
  lon = atan2 (y,x)
  if (lon.lt.0.d0 ) then
    lon = lon+2.d0*pi
  endif
  !
  ! Now return
  plh(1) = lon
  plh(2) = lat
  plh(3) = alt
end subroutine gwcs_xyz2lla
!
subroutine gwcs_lla2xyz (plh, xyz, A, FL)
  !---------------------------------------------------------------------
  ! @ public
  ! Converts elliptic Longitude, Latitude and Height to geocentric X,Y,Z
  !
  ! references:
  ! Escobal, "Methods of Orbit Determination", 1965, Wiley & Sons, Inc.,
  ! pp. 27-29.
  !---------------------------------------------------------------------
  real(8), intent(in) :: plh(3)  ! Longitude, Latitude (in radians) and height (in unit of distance)
  real(8), intent(out) :: xyz(3) ! XYZ coordinates (in units of distance)
  real(8), intent(in) :: a       ! semi-major axis of ellipsoid (in units are of distance)
  real(8), intent(in) :: fl      ! flattening of ellipsoid [unitless]
  !
  ! Local
  real(8) :: flatfn, funsq
  real(8) :: lon,lat,alt
  real(8) :: slat, g1,g2
  real(8) :: x,y,z
  !
  flatfn = (2.d0-fl)*fl
  funsq = (1.d0-fl)**2
  !
  lon = plh(1)
  lat = plh(2)
  alt = plh(3)
  !
  slat = sin(lat)
  g1 = A / sqrt(1.d0 - flatfn*slat**2)
  g2 = g1*funsq + alt
  g1 = g1 + alt
  !
  x = g1 * cos(lat)
  y = x * sin(lon)
  x = x * cos(lon)
  z = g2 * slat
  !
  xyz(1) = x
  xyz(2) = y
  xyz(3) = z
end subroutine gwcs_lla2xyz
!
subroutine gwcs_azel2pa(lat,az,el,parang)
  !---------------------------------------------------------------------
  ! @ public
  ! Compute the parallactic angle from az/el and latitude of
  ! observation. Azimuth convention must be South at 180 degrees.
  ! If using azimuth South at 0 degrees, sinAz and cosAz sign should be
  ! swapped.
  !---------------------------------------------------------------------
  real(kind=8), intent(in)  :: lat     ! [rad]
  real(kind=8), intent(in)  :: az,el   ! [rad]
  real(kind=8), intent(out) :: parang  ! [rad]
  ! Local
  real(kind=8) :: cosLat,sinLat
  real(kind=8) :: cosAz,sinAz
  real(kind=8) :: cosEl,sinEl
  real(kind=8) :: dec,cosDec,sinDec
  real(kind=8) :: cosParallactic,sinParallactic
  !
  sinLat = sin(lat)
  cosLat = cos(lat)
  !
  sinAz = sin(az)
  cosAz = cos(az)
  !
  sinEl = sin(el)
  cosEl = cos(el)
  !
  sinDec = sinLat * sinEl + cosLat * cosEl * cosAz
  dec = asin(sinDec)  ! [rad]
  cosDec = cos(dec)
  !
  sinParallactic = - cosLat * sinAz / cosDec
  cosParallactic = (sinLat - sinDec * sinEl) / cosDec / cosEl
  parang = atan2(sinParallactic,cosParallactic)  ! [rad]
end subroutine gwcs_azel2pa
