!-------------------------------------- LICENCE BEGIN ------------------------------------
!Environment Canada - Atmospheric Science and Technology License/Disclaimer,
!                     version 3; Last Modified: May 7, 2008.
!This is free but copyrighted software; you can use/redistribute/modify it under the terms
!of the Environment Canada - Atmospheric Science and Technology License/Disclaimer
!version 3 or (at your option) any later version that should be found at:
!http://collaboration.cmc.ec.gc.ca/science/rpn.comm/license.html
!
!This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
!without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
!See the above mentioned License/Disclaimer for more details.
!You should have received a copy of the License/Disclaimer along with this software;
!if not, you can write to: EC-RPN COMM Group, 2121 TransCanada, suite 500, Dorval (Quebec),
!CANADA, H9P 1J3; or send e-mail to service.rpn@ec.gc.ca
!-------------------------------------- LICENCE END --------------------------------------
!

subroutine oda_H(lcolumn,lcolumng,lobsSpaceData) 3,19
  use mpivar_mod
  use topLevelControl_mod
  use EarthConstants_mod
  use MathPhysConstants_mod
  use obsSpaceData_mod
  use columnData_mod
  use bufr
  use modgpsro_mod
  use modgpsztd_mod
  use minimization_mod
  use timeCoord_mod
  use filterObs_mod
  implicit none
  !
  !Purpose:
  !Compute simulated observations from profiled model
  !increments.
  !It returns Hdx in OBS_WORK
  !Calls the several linear observation operators
  !
  !Author  : S. Pellerin *ARMA/MRB January 2009
  !
  !Revision:
  ! L. Fillion, ARMA/EC, 5 Jun 2009. Introduce 1 Obs experiment.
  ! S. Macpherson ARMA  11 Sep 2009
  !            - added ground-based GPS (ZTD) observation operator
  ! S. Macpherson ARMA   5 Oct 2012
  !            - update oda_Hgp for new ZTD operator
  ! S. Macpherson ARMA  14 Jan 2013
  !            - use numGPSZTD (from new modgpsztd_mod) to determine if call oda_Hgp needed
  !            - modified oda_Hgp
  !            - use new ZTD-specific GPS modules modgps04profilezd, modgps08ztdop
  !            - merged with latest version (oda_Hro) from Josep (Rev.213M)
  !            - like oda_Hro, use OpenMP and Jacobian storage for GPS ZTD
  !
  !Local declarations
  integer, save :: nl_ncall = 0
  real*8 :: dl_bidon

  type(struct_columnData) :: lcolumn,lcolumng
  type(struct_obs) :: lobsSpaceData
  type(struct_vco), pointer :: vco_anl

  IF(mpi_myid == 0) THEN
    write(*,*)'ODA_H- Linearized observation operators'
  endif

  vco_anl => col_getVco(lcolumng)

  nl_ncall = nl_ncall + 1

  call tmg_start(42,'OBS_PPP_TLAD') !
  call oda_Hpp              ! fill in OBS_WORK : Hdx
  call tmg_stop(42)

  call tmg_start(43,'OBS_SFC_TLAD')
  call oda_Hsf              ! fill in OBS_WORK : Hdx
  call tmg_stop (43)

  call tmg_start(44,'OBS_TOV_TLAD') !
  call oda_Hto              ! fill in OBS_WORK : Hdx
  call tmg_stop (44)

  call tmg_start(45,'OBS_GPSRO_TLAD') !
  call oda_Hro
  call tmg_stop (45)        !

  call tmg_start(46,'OBS_ZZZ_TLAD') !
  call oda_Hzp
  call tmg_stop (46)        !

  call tmg_start(47,'OBS_GPSGB_TLAD') !
  if (numGPSZTD > 0)  call oda_Hgp
  call tmg_stop (47)        !


  CONTAINS


      SUBROUTINE oda_Hpp 1,55
!*
!* Purpose: Compute simulated Upper Air observations from profiled model
!*          increments.
!*          It returns Hdx in OBS_WORK
!*          Interpolate vertically the contents of commvo to
!*          the pressure levels of the observations.
!*          A linear interpolation in ln(p) is performed.
!*
!*implicits
      implicit none
      INTEGER IPB,IPT
      INTEGER INDEX_HEADER,INDEX_FAMILY,IK
      INTEGER J,INDEX_BODY,ITYP,nlev_T
      REAL*8 ZDADPS,ZCON
      REAL*8 ZWB,ZWT, ZEXP, ZGAMMA,ZLTV,ZTVG,ZPPOST
      REAL*8 ZLEV,ZPT,ZPB,ZLAT,ZLON,ZTORAD
      REAL*8 dPdPsT,dPdPsB
      REAL*8 columnVarB,columnVarT,columngVarB,columngVarT,lqtoes,lqtoes_tl
      LOGICAL LLASSIM,LLDIAG
      INTEGER, PARAMETER :: numFamily=3
      CHARACTER(len=2) :: list_family(numFamily),varType
      !
      !     Temperature lapse rate for extrapolation of gz below model surface
      !
      zgamma = 0.0065D0 / GRAV
      zexp = MPC_RGAS_DRY_AIR_R8*zgamma

      list_family(1) = 'UA'
      list_family(2) = 'AI'
      list_family(3) = 'SW'

      FAMILY: do index_family=1,numFamily

        call obs_set_current_body_list(lobsSpaceData,list_family(index_family))
        BODY: DO 
          index_body = obs_getBodyIndex(lobsSpaceData)
          if (index_body < 0) exit BODY

          llassim= (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body) .EQ. 1) &
             .AND. (obs_bodyElem_i(lobsSpaceData,OBS_XTR,index_body) .EQ. 0) &
             .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body) .EQ. 2)
          lldiag = (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body) .EQ. -1) &
             .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body) .EQ. 2)
          IF (llassim .or. lldiag) THEN
            index_header = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
            ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
            ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
            varType = vnl_varTypeFromVarnum(ityp)
            IK   = obs_bodyElem_i(lobsSpaceData,OBS_LYR,INDEX_BODY)
            IPT  = IK + col_getOffsetFromVarno(lcolumng,ityp)
            IPB  = IPT+1
            ZPT    = col_getPressure(LCOLUMNG,IK  ,INDEX_HEADER,varType)
            ZPB    = col_getPressure(LCOLUMNG,IK+1,INDEX_HEADER,varType)
            dPdPsT = col_getPressureDeriv(LCOLUMNG,IK  ,INDEX_HEADER,varType)
            dPdPsB = col_getPressureDeriv(LCOLUMNG,IK+1,INDEX_HEADER,varType)
            ZWB  = LOG(ZLEV/ZPT)/LOG(ZPB/ZPT)
            ZWT  = 1.0D0 - ZWB

            ZDADPS   = ( LOG(ZLEV/ZPB)*dPdPsT/ZPT -   &
                         LOG(ZLEV/ZPT)*dPdPsB/ZPB )/  &
                       LOG(ZPB/ZPT)**2

            if(ityp.eq.bufr_nees) then
              columnVarB=lqtoes_tl(col_getElem(lcolumn,IK+1,INDEX_HEADER,'HU'), &
                                   col_getElem(lcolumn,IK+1,INDEX_HEADER,'TT'), &
                                   col_getElem(lcolumn,1,INDEX_HEADER,'P0'), &
                                   col_getElem(lcolumng,IK+1,INDEX_HEADER,'HU'), &
                                   col_getPressure(lcolumng,IK+1,INDEX_HEADER,'TH'), &
                                   dPdPsB)
              columnVarT=lqtoes_tl(col_getElem(lcolumn,IK  ,INDEX_HEADER,'HU'), &
                                   col_getElem(lcolumn,IK  ,INDEX_HEADER,'TT'), &
                                   col_getElem(lcolumn,1,INDEX_HEADER,'P0'), &
                                   col_getElem(lcolumng,IK  ,INDEX_HEADER,'HU'), &
                                   col_getPressure(lcolumng,IK  ,INDEX_HEADER,'TH'),  &
                                   dPdPsT)
              columngVarB=lqtoes(col_getElem(lcolumng,IK+1,INDEX_HEADER,'HU'), &
                                 col_getElem(lcolumng,IK+1,INDEX_HEADER,'TT'), &
                                 col_getPressure(lcolumng,IK+1,INDEX_HEADER,'TH'))
              columngVarT=lqtoes(col_getElem(lcolumng,IK  ,INDEX_HEADER,'HU'), &
                                 col_getElem(lcolumng,IK  ,INDEX_HEADER,'TT'), &
                                 col_getPressure(lcolumng,IK  ,INDEX_HEADER,'TH'))
            else
              columnVarB=col_getElem(lcolumn,IPB,INDEX_HEADER)
              columnVarT=col_getElem(lcolumn,IPT,INDEX_HEADER)
              columngVarB=col_getElem(lcolumng,IPB,INDEX_HEADER)
              columngVarT=col_getElem(lcolumng,IPT,INDEX_HEADER)
            endif
            call obs_bodySet_r(lobsSpaceData,OBS_WORK,INDEX_BODY,   &
                 ZWB*columnVarB + ZWT*columnVarT+  &
                 (columngVarB - columngVarT)*  &
                 ZDADPS*col_getElem(LCOLUMN,1,INDEX_HEADER,'P0'))
          elseif( (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body) .EQ. 1) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_XTR,index_body) .EQ. 2) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body) .EQ. 2) ) then
            INDEX_HEADER = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
            ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
            !
            ! TL model for height data below model's orography
            !
            nlev_T = col_getNumLev(LCOLUMN,'TH')
            ZLTV = lcolumng%OLTV(1,nlev_T,INDEX_HEADER)*col_getElem(LCOLUMN,nlev_T,INDEX_HEADER,'TT') &
                 + lcolumng%OLTV(2,nlev_T,INDEX_HEADER)*col_getElem(LCOLUMN,nlev_T,INDEX_HEADER,'HU')
            ZTVG = lcolumng%OLTV(1,nlev_T,INDEX_HEADER)*col_getElem(lcolumng,nlev_T,INDEX_HEADER,'TT')
            ZCON =(ZLEV/col_getElem(lcolumng,1,INDEX_HEADER,'P0'))**ZEXP
            call obs_bodySet_r(lobsSpaceData,OBS_WORK,index_body, (1.d0-zcon)/zgamma*ZLTV &
                + MPC_RGAS_DRY_AIR_R8*ZTVG*zcon*col_getElem(lcolumn,1,index_header,'P0') &
                /col_getElem(lcolumng,1,index_header,'P0'))
          endif

        enddo BODY

      enddo FAMILY

      end subroutine oda_Hpp



      SUBROUTINE oda_Hsf 1,50
!*
!* Purpose: Compute simulated surface observations from profiled model
!*          increments.
!*          It returns Hdx in OBS_WORK
!*
      IMPLICIT NONE

      INTEGER IPB,IPT,IXTR
      INTEGER INDEX_HEADER,IK
      INTEGER J,INDEX_BODY,ITYP,INDEX_FAMILY,nlev
      REAL*8 ZCON
      REAL*8 ZWB,ZWT, ZEXP,ZEXPGZ,ZGAMMA,ZLTV,ZTVG,ZPPOST
      REAL*8 ZLEV,ZPT,ZPB,ZDELPS,ZDELTV,ZGAMAZ,ZHHH
      REAL*8 columnVarB,lqtoes_tl
      REAL*8 dPdPsfc
      INTEGER, PARAMETER :: numFamily=4
      CHARACTER(len=2) :: list_family(numFamily),varType
!C
!C     Temperature lapse rate for extrapolation of gz below model surface
!C
      zgamma = 0.0065d0 / GRAV
      zexp   = 1.0d0/(MPC_RGAS_DRY_AIR_R8*zgamma)
      zexpGZ = MPC_RGAS_DRY_AIR_R8*zgamma
!C
!C
      list_family(1) = 'UA'
      list_family(2) = 'SF'
      list_family(3) = 'SC'
      list_family(4) = 'GP'

      FAMILY: do index_family=1,numFamily

        call obs_set_current_body_list(lobsSpaceData, list_family(index_family))
        BODY: do
          index_body = obs_getBodyIndex(lobsSpaceData)
          if (index_body < 0) exit BODY
  
! Process all data within the domain of the model
          ityp = obs_bodyElem_i(lobsSpaceData,OBS_VNM,index_body)
          if ( ityp.eq.bufr_nezd ) cycle BODY
          if(    (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body).eq.1) &
           .and. (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body).eq.1) &
           .and. (ityp.eq.bufr_nets .or. ityp.eq.bufr_neps  &
             .or. ityp.eq.bufr_nepn .or. ityp.eq.bufr_ness  &
             .or. ityp.eq.bufr_neus .or. ityp.eq.bufr_nevs  &
             .or. obs_bodyElem_i(lobsSpaceData,OBS_XTR,index_body).eq.0) ) then

            if( ityp.eq.bufr_neus .or. ityp.eq.bufr_nevs ) then
              varType = 'MM'
            else
              varType = 'TH'
            endif
            nlev = col_getNumLev(lcolumn,varType)
            INDEX_HEADER = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
            ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
            IXTR = obs_bodyElem_i(lobsSpaceData,OBS_XTR,INDEX_BODY)
            IK   = obs_bodyElem_i(lobsSpaceData,OBS_LYR,INDEX_BODY)
            ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
            ZHHH = ZLEV * GRAV
            IPT  = nlev - 1 + col_getOffsetFromVarno(lcolumng,ityp)
            IPB  = IPT+1

            IF (ITYP.EQ.BUFR_NETS .OR. ITYP.EQ.BUFR_NESS .OR.  &
                ITYP.EQ.BUFR_NEUS .OR. ITYP.EQ.BUFR_NEVS) THEN
              if(ITYP.eq.BUFR_NESS) THEN
                dPdPsfc = col_getPressureDeriv(lcolumng,nlev,index_header,'TH')
                columnVarB=lqtoes_tl(col_getElem(lcolumn,nlev,INDEX_HEADER,'HU'), &
                                     col_getElem(lcolumn,nlev,INDEX_HEADER,'TT'), &
                                     col_getElem(lcolumn,1,INDEX_HEADER,'P0'), &
                                     col_getElem(lcolumng,nlev,INDEX_HEADER,'HU'), &
                                     col_getPressure(lcolumng,nlev,INDEX_HEADER,varType),  &
                                     dPdPsfc)
              else
                columnVarB=col_getElem(LCOLUMN,IPB,INDEX_HEADER)
              endif
              call obs_bodySet_r(lobsSpaceData,OBS_WORK,INDEX_BODY,columnVarB)
            ELSEIF (ITYP.EQ.BUFR_NEPS .OR. ITYP.EQ.BUFR_NEPN) THEN
              ZLTV  = lcolumng%OLTV(1,nlev,INDEX_HEADER)*col_getElem(LCOLUMN,nlev,INDEX_HEADER,'TT')  & 
                    + lcolumng%OLTV(2,nlev,INDEX_HEADER)*col_getElem(LCOLUMN,nlev,INDEX_HEADER,'HU')
              ZTVG  = lcolumng%OLTV(1,nlev,INDEX_HEADER)*col_getElem(lcolumng,nlev,INDEX_HEADER,'TT')
              ZGAMAZ= ZGAMMA*(ZHHH-col_getHeight(lcolumng,nlev,INDEX_HEADER,varType))
              ZCON  = ((ZTVG-ZGAMAZ)/ZTVG)
              ZDELPS= (col_getElem(LCOLUMN,1,INDEX_HEADER,'P0')*ZCON**ZEXP)
              ZDELTV= ((col_getElem(lcolumng,1,INDEX_HEADER,'P0')*ZEXP*ZCON**(ZEXP-1))  &
                   *(ZGAMAZ/(ZTVG*ZTVG)*ZLTV))
              call obs_bodySet_r(lobsSpaceData,OBS_WORK,INDEX_BODY, ZDELPS+ZDELTV)
            ELSE
              IPT  = IK + col_getOffsetFromVarno(lcolumng,ityp)
              IPB  = IPT+1
              ZPT  = col_getHeight(lcolumng,IK,INDEX_HEADER,varType)
              ZPB  = col_getHeight(lcolumng,IK+1,INDEX_HEADER,varType)
              ZWB  = (ZPT-ZHHH)/(ZPT-ZPB)
              ZWT  = 1.d0 - ZWB
              call obs_bodySet_r(lobsSpaceData,OBS_WORK,INDEX_BODY,  &
                   ZWB*col_getElem(LCOLUMN,IPB,INDEX_HEADER) + ZWT*col_getElem(LCOLUMN,IPT,INDEX_HEADER)+  &
                   (col_getElem(lcolumng,IPB,INDEX_HEADER)-col_getElem(lcolumng,IPT,INDEX_HEADER)))
            ENDIF
          elseif( (obs_bodyElem_i(lobsSpaceData,OBS_ASS,index_body) .EQ. 1) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_XTR,index_body) .EQ. 2) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VNM,index_body) .EQ. BUFR_NEGZ ) &
            .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,index_body) .EQ. 1)    ) then

            nlev = col_getNumLev(lcolumn,'TH')
            INDEX_HEADER = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
            ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
!C
!C                 CONTRIBUTION TO Jo
!C
!c  TL model for height data below model's orography
!c
            ZLTV = lcolumng%OLTV(1,nlev,INDEX_HEADER)*col_getElem(LCOLUMN,nlev,INDEX_HEADER,'TT') &
                 + lcolumng%OLTV(2,nlev,INDEX_HEADER)*col_getElem(LCOLUMN,nlev,INDEX_HEADER,'HU')
            ZTVG = lcolumng%OLTV(1,nlev,INDEX_HEADER)*col_getElem(lcolumng,nlev,INDEX_HEADER,'TT')
            ZCON=(ZLEV/col_getElem(lcolumng,1,INDEX_HEADER,'P0'))**ZEXPGZ
            call obs_bodySet_r(lobsSpaceData,OBS_WORK,index_body,(1.d0-zcon)/zgamma*ZLTV &
                + MPC_RGAS_DRY_AIR_R8*ZTVG*zcon*col_getElem(lcolumn,1,index_header,'P0') &
                /col_getElem(lcolumng,1,index_header,'P0'))
          endif

        enddo BODY

      enddo FAMILY

      END subroutine oda_Hsf


      subroutine oda_Hto 1,7
  !
  ! Purpose: Compute simulated radiances observations from profiled model
  !          increments.
  !          It returns Hdx in OBS_WORK
  !
  !author        : j. halle *cmda/aes  april 8, 2005
  !
  !revision 001  : a. beaulne *cmda/smc  july 2006
  !                    -addition of geopotential field in call to
  !                     tovs_fill_profiles
  !                S. Pellerin, ARMA, August 2008
  !                    - Avoid multiple (iterative) interpolation to 43 levels
  !                      background variable profiles
  !                S. Pellerin, ARMA, January 2009
  !                    - call to oda_storeHdx_radiances instead computing Jo
  !
      implicit none
      integer :: datestamp

  !     1.   Prepare atmospheric profiles for all tovs observation points for use in rttov
  !     .    -----------------------------------------------------------------------------
  !
  if (min_nsim == 1) then
     datestamp = tim_getDatestamp()
     call tovs_fill_profiles(lcolumng,lobsSpaceData,datestamp,filt_rlimlvhu,top_bgckIrMode())
  endif

  !     2.   Prepare atmospheric tl profiles for all tovs observation points for use in rttov
  !     .    --------------------------------------------------------------------------------
  !
  call tovs_fill_profiles_tl(lcolumn,lcolumng,top_bgckIrMode())

  !     3.   Compute radiance
  !     .    ----------------
  !
  call tovs_rttov_tl(lobsSpaceData)


  call oda_storeHdx_radiances(lobsSpaceData)

  return

end subroutine oda_Hto



      SUBROUTINE oda_Hro 1,42
!*
!* Purpose: Compute the tangent operator for GPSRO observations.
!*
!*Author  : J. M. Aparicio Jan 2004
!*Modified: J. M. Aparicio Dec 2012 adapt to accept bending angle data
!*    -------------------
      use modgps00base      , only : ngpscvmx
      use modgps01ctphys    , only : p_TC, p_knot
      use modgps02wgs84grav , only : gpsgravitysrf
      use modgps03diff
      use modgps04profile   , only : gpsprofile, gpsstruct1sw
      use modgps08refop     , only : gpsrefopv
      use modgps09bend      , only : gpsbndopv1
      use IndexListDepot_mod, only : struct_index_list
      implicit none

      REAL*8 zLat, Lat, sLat
      REAL*8 zLon, Lon
      REAL*8 zAzm, Azm
      INTEGER IAZM, ISAT
      REAL*8 Rad, Geo, WFGPS
      REAL*8, allocatable :: zPP(:)
      REAL*8, allocatable :: zDP(:)
      REAL*8, allocatable :: zTT(:)
      REAL*8, allocatable :: zHU(:)
      REAL*8, allocatable :: zUU(:)
      REAL*8, allocatable :: zVV(:)
      REAL*8 zMT,radw

      REAL*8 ZMHXL
      REAL*8 DX (ngpscvmx)

      INTEGER IDATYP
      INTEGER JL, JV, NGPSLEV, NWNDLEV, stat1, JJ
      integer :: index_header, index_body, iProfile
      type(struct_index_list), pointer :: local_current_list

      LOGICAL  ASSIM, LFIRST

      INTEGER NH, NH1
      TYPE(GPSPROFILE)           :: PRF
      REAL*8       , ALLOCATABLE :: H   (:),AZMV(:)
      TYPE(GPSDIFF), ALLOCATABLE :: RSTV(:),RSTVP(:),RSTVM(:)
!C      WRITE(*,*)'ENTER oda_Hro'
!C
!C     * 1.  Initializations
!C     *     ---------------
!C
      NGPSLEV=col_getNumLev(lcolumn,'TH')
      NWNDLEV=col_getNumLev(lcolumn,'MM')
      LFIRST=.FALSE.
      if ( .NOT.allocated(vGPSRO_Jacobian) ) then
         LFIRST = .TRUE.
         allocate(zPP (NGPSLEV))
         allocate(zDP (NGPSLEV))
         allocate(zTT (NGPSLEV))
         allocate(zHU (NGPSLEV))
         allocate(zUU (NGPSLEV))
         allocate(zVV (NGPSLEV))

         allocate(vGPSRO_Jacobian(numGPSROProfiles,GPSRO_MAXPRFSIZE,2*NGPSLEV+1))
         allocate(vGPSRO_lJac    (numGPSROProfiles))
         vGPSRO_lJac=.false.

         allocate( H    (GPSRO_MAXPRFSIZE) )
         allocate( AZMV (GPSRO_MAXPRFSIZE) )
         allocate( RSTV (GPSRO_MAXPRFSIZE) )
!C         IF (LEVELGPSRO.EQ.1) THEN
!C            allocate( RSTVP(GPSRO_MAXPRFSIZE) )
!C            allocate( RSTVM(GPSRO_MAXPRFSIZE) )
!C         ENDIF
      endif
!C
!C    Loop over all header indices of the 'RO' family (Radio Occultation)
!C
      ! Set the header list (start at the beginning of the list)
      call obs_set_current_header_list(lobsSpaceData,'RO')
!##$omp parallel default(shared) &
!##$omp private(index_header,idatyp,assim,nh,local_current_list,index_body) &
!##$omp private(iProfile,irad,igeo,iazm,isat,rad,geo,zazm,zmt,wfgps,jj) &
!##$omp private(zlat,zlon,lat,lon,azm,slat) &
!##$omp private(stat1,jl,zpp,zdp,ztt,zhu,zuu,zvv,prf,dx) &
!##$omp private(h,azmv,rstv,rstvp,rstvm,nh1,zmhxl,jv)
      nullify(local_current_list)
      HEADER: do
         INDEX_HEADER = obs_getHeaderIndex(lobsSpaceData)
         if (INDEX_HEADER < 0) exit HEADER
!C
!C     * Process only refractivity data (codtyp 169)
!C
         IDATYP = obs_headElem_i(lobsSpaceData,OBS_ITY,INDEX_HEADER)
         DATYP: IF ( IDATYP .EQ. 169 ) THEN
!C
!C     *    Scan for requested data values of the profile, and count them
!C
            ASSIM = .FALSE.
            NH = 0
!C
!C     *    Loop over all body indices for this index_header:
!C     *    (start at the beginning of the list)
!C
            call obs_set_current_body_list(lobsSpaceData, INDEX_HEADER, &
                 current_list=local_current_list)
            BODY: do 
               index_body = obs_getBodyIndex(local_current_list)
               if (index_body < 0) exit BODY
               IF ( obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1 ) THEN
                  ASSIM = .TRUE.
                  NH = NH + 1
               ENDIF
            ENDDO BODY
!C
!C     *    If assimilations are requested, prepare and apply the observation operator
!C
            ASSIMILATE: IF (ASSIM) THEN
               iProfile=iProfile_from_index(INDEX_HEADER)
!C
!C     *       Profile at the observation location:
!C
               if (.not.vGPSRO_lJac(iProfile)) then
!C
!C     *          Basic geometric variables of the profile:
!C
                  zLat = obs_headElem_r(lobsSpaceData,OBS_LAT,INDEX_HEADER)
                  zLon = obs_headElem_r(lobsSpaceData,OBS_LON,INDEX_HEADER)
                  IAZM = obs_headElem_i(lobsSpaceData,OBS_AZA,INDEX_HEADER)
                  ISAT = obs_headElem_i(lobsSpaceData,OBS_SAT,INDEX_HEADER)
                  Rad  = obs_headElem_r(lobsSpaceData,OBS_TRAD,INDEX_HEADER)
                  Geo  = obs_headElem_r(lobsSpaceData,OBS_GEOI,INDEX_HEADER)
                  zAzm = 0.01d0*IAZM / MPC_DEGREES_PER_RADIAN_R8
                  zMT  = col_getHeight(lcolumng,NGPSLEV,INDEX_HEADER,'TH')/RG
                  WFGPS= 0.d0
                  DO JJ=1,NUMGPSSATS
                     IF (ISAT.EQ.IGPSSAT(JJ)) WFGPS=WGPS(JJ)
                  ENDDO
                  Lat  = zLat * MPC_DEGREES_PER_RADIAN_R8
                  Lon  = zLon * MPC_DEGREES_PER_RADIAN_R8
                  Azm  = zAzm * MPC_DEGREES_PER_RADIAN_R8
                  sLat = sin(zLat)
                  zMT  = zMT * RG / gpsgravitysrf(sLat)
                  DO JL = 1, NGPSLEV
!C
!C     *             Profile x_b
!C
                     zPP(JL) = col_getPressure(lcolumng,JL,INDEX_HEADER,'TH')
!C     *             True implementation of zDP (dP/dP0)
                     zDP(JL) = col_getPressureDeriv(lcolumng,JL,INDEX_HEADER,'TH')
                     zTT(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'TT') - p_TC
                     zHU(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'HU')
                     zUU(JL) = 0.d0
                     zVV(JL) = 0.d0
                  ENDDO
                  DO JL = 1, NWNDLEV
                     zUU(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'UU') * p_knot
                     zVV(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'VV') * p_knot
                  ENDDO
                  zUU(NGPSLEV) = zUU(NWNDLEV)
                  zVV(NGPSLEV) = zUU(NWNDLEV)
!C     
!C     *          GPS profile structure:
!C
                  call gpsstruct1sw(ngpslev,zLat,zLon,zAzm,zMT,Rad,geo,zPP,zDP,zTT,zHU,zUU,zVV,prf)
!C
!C     *          Prepare the vector of all the observations:
!C
                  NH1 = 0
                  call obs_set_current_body_list(lobsSpaceData, index_header, &
                       current_list=local_current_list)
                  BODY_2: do 
                     INDEX_BODY = obs_getBodyIndex(local_current_list)
                     if (INDEX_BODY < 0) exit BODY_2
                     IF ( obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1 ) THEN
                        NH1      = NH1 + 1
                        H(NH1)   = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
                        AZMV(NH1)= zAzm
                     ENDIF
                  ENDDO BODY_2
!C
!C     *          Apply the observation operator:
!C
                  IF (LEVELGPSRO.EQ.1) THEN
                     CALL GPSBNDOPV1(H      , AZMV, NH, PRF, RSTV)
!C                     CALL GPSBNDOPV1(H+WFGPS, AZMV, NH, PRF, RSTVP)
!C                     CALL GPSBNDOPV1(H-WFGPS, AZMV, NH, PRF, RSTVM)
!C                     do nh1 = 1, nh
!C                        RSTV(nh1)=(RSTVP(nh1)+RSTV(nh1)+RSTVM(nh1))/3.d0
!C                     enddo
                  ELSE
                     CALL GPSREFOPV (H,       NH, PRF, RSTV)
                  ENDIF
                  DO NH1=1,NH
                     vGPSRO_Jacobian(iProfile,NH1,:)= RSTV(NH1)%DVAR(1:2*NGPSLEV+1)
                  ENDDO
                  vGPSRO_lJac(iProfile)=.true.
               endif
!C
!C     *       Local vector state
!C
               DO JL = 1, NGPSLEV
                  DX (        JL) = col_getElem(LCOLUMN,JL,index_header,'TT')
                  DX (NGPSLEV+JL) = col_getElem(LCOLUMN,JL,index_header,'HU')
               ENDDO
               DX (2*NGPSLEV+1)   = col_getElem(LCOLUMN,1 ,index_header,'P0')
!C
!C     *       Perform the (H(xb)DX-Y') operation
!C     *       Loop over all body indices for this index_header:
!C
               NH1 = 0
               call obs_set_current_body_list(lobsSpaceData, index_header, &
                    current_list=local_current_list)
               BODY_3: do 
                  INDEX_BODY = obs_getBodyIndex(local_current_list)
                  if (INDEX_BODY < 0) exit BODY_3
                  IF ( obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY).EQ.1 ) THEN
                     NH1 = NH1 + 1
!C
!C     *             Evaluate H(xb)DX
!C
                     ZMHXL = 0.d0
                     DO JV = 1, 2*NGPSLEV+1
                        ZMHXL = ZMHXL + vGPSRO_Jacobian(iProfile,NH1,JV) * DX(JV)
                     ENDDO
!C
!C     *             Store in CMA
!C
                     call obs_bodySet_r(lobsSpaceData,OBS_WORK,INDEX_BODY, ZMHXL)
                  ENDIF
               ENDDO BODY_3
            ENDIF ASSIMILATE
         ENDIF DATYP
      ENDDO HEADER
!##$omp end parallel

      IF (LFIRST) THEN
!C         IF (LEVELGPSRO.EQ.1) THEN
!C            deallocate( RSTVM )
!C            deallocate( RSTVP )
!C         ENDIF
         deallocate( RSTV )
         deallocate( AZMV )
         deallocate( H    )

         deallocate(zVV)
         deallocate(zUU)
         deallocate(zHU)
         deallocate(zTT)
         deallocate(zDP)
         deallocate(zPP)
      ENDIF

!C      WRITE(*,*)'EXIT oda_Hro'
      RETURN
      END subroutine oda_Hro



      SUBROUTINE oda_Hzp 1,20
!*
!* Purpose: Compute simulated profiler observations from profiled model
!*          increments.
!*          It returns Hdx in OBS_WORK
!*          Interpolate vertically the contents of commvo to heights
!*          (in meters) of the observations.
!*          A linear interpolation in z is performed.
!*
!*Author  :  J. St-James, CMDA/SMC July 2003

      implicit none
      INTEGER IPB,IPT
      INTEGER INDEX_HEADER,IK
      INTEGER J,INDEX_BODY,ITYP
      REAL*8 ZVAR,ZDA1,ZDA2
      REAL*8 ZWB,ZWT, ZLTV,ZTVG,ZPPOST
      REAL*8 ZLEV,ZPT,ZPB,ZLAT,ZLON,ZTORAD,ZDENO
      LOGICAL LLOK, LLPRINT, LLUV
      character(len=2) :: varType

      call obs_set_current_body_list(lobsSpaceData, 'PR')
      BODY: do
        index_body = obs_getBodyIndex(lobsSpaceData)
        if (index_body < 0) exit BODY

        IF (   (obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY) .EQ. 1) &
         .AND. (obs_bodyElem_i(lobsSpaceData,OBS_XTR,INDEX_BODY) .EQ. 0) &
         .AND. (obs_bodyElem_i(lobsSpaceData,OBS_VCO,INDEX_BODY) .EQ. 1)  ) THEN
          INDEX_HEADER = obs_bodyElem_i(lobsSpaceData,OBS_HIND,INDEX_BODY)
          ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
          IK   = obs_bodyElem_i(lobsSpaceData,OBS_LYR,INDEX_BODY)
          ITYP = obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY)
          varType = vnl_varTypeFromVarnum(ityp)
          IPT  = IK + col_getOffsetFromVarno(lcolumng,ityp)
          IPB  = IPT+1
          ZPT  = col_getHeight(lcolumng,IK  ,INDEX_HEADER,varType)/RG
          ZPB  = col_getHeight(lcolumng,IK+1,INDEX_HEADER,varType)/RG
          ZDENO= ZPT-ZPB
          ZWB  = (ZPT-ZLEV)/ZDENO
          ZWT  = 1.0D0 - ZWB

          ZDA1= (ZLEV-ZPB)/(ZDENO**2)
          ZDA2= (ZPT-ZLEV)/(ZDENO**2)

          if(ITYP.eq.BUFR_NEES) then
            write(*,*) 'ABORTING IN ODA_HZP: CANNOT ASSIMILATE ES!!!',ityp,obs_getfamily(lobsSpaceData,index_header),index_header,index_body
            call abort3d('Aborting in oda_H')
          endif
          call obs_bodySet_r(lobsSpaceData,OBS_WORK,INDEX_BODY,  &
              ZWB*col_getElem(LCOLUMN,IPB,INDEX_HEADER) + ZWT*col_getElem(LCOLUMN,IPT,INDEX_HEADER) +  &
              (col_getElem(lcolumng,IPB,INDEX_HEADER) - col_getElem(lcolumng,IPT,INDEX_HEADER))*  &
              (ZDA1*col_getHeight(LCOLUMN,IK,INDEX_HEADER,varType)/RG + ZDA2*col_getHeight(LCOLUMN,IK+1,INDEX_HEADER,varType)/RG))
        ENDIF
      ENDDO BODY
      RETURN
      END subroutine oda_Hzp



      SUBROUTINE oda_Hgp 1,60
!*
!***s/r  -oda_Hgp TL of DOBSGPSGB (Jo for GB-GPS ZTD observations)
!*
!*
!*Author  : S. Macpherson *ARMA October 2012
!*    -------------------
!**    Purpose: Compute H'dx for all GPS ZTD observations
!*

      use modgps00base      , only : ngpscvmx
      use modgps01ctphys    , only : p_TC
      use modgps03diff      , only : gpsdiff
      use modgps04profilezd , only : gpsprofilezd, gpsstructztd, gpsdpress
      use modgps08ztdop     , only : gpsZTDopv, gpsPW
      implicit none

      REAL*8 ZLAT, Lat
      REAL*8 ZLON, Lon
      REAL*8, allocatable :: ZTTB(:)
      REAL*8, allocatable :: ZHUB(:)
      REAL*8, allocatable :: ZPPB(:)
      REAL*8, allocatable :: ZDP(:)
      REAL*8 ZP0B, ZPSMOD, ZPWMOD, ZPWMOD2, dZTD
      REAL*8 ZMT
      real*8 sfcfield

      REAL*8 ZHX, ZLEV, ZDZMIN
      REAL*8 JAC(ngpscvmx)
      REAL*8 DX (ngpscvmx)

      INTEGER INDEX_HEADER, INDEX_BODY
      INTEGER JL, NFLEV, status, iztd, icount, NFLEV2, stat, iversion

      LOGICAL      ASSIM, LSTAG
      CHARACTER*2  varType
      
      real*8, dimension(:), pointer :: dpdp0 => null()

      TYPE(gpsprofilezd)   :: PRF, PRF2
      TYPE(gpsdiff)        :: ZTDOPV, ZTDOPV2
      
!      WRITE(*,*)'ENTER oda_Hgp'

      stat = vgd_get(vco_anl%vgrid,key='ig_1 - vertical coord code',value=iversion)
      if (iversion .ne. 5001) then
         LSTAG = .TRUE. 
         varType = 'TH'
      else
         LSTAG = .FALSE.
         varType = 'TH'
      endif
      
      ZDZMIN = DZMIN                     ! from modgpsztd_mod
      
      NFLEV  = col_getNumLev(lcolumng,'TH')
      NFLEV2 = col_getNumLev(lcolumn,'TH')
      
!C
!C     * 1.  Initializations
!C     *     ---------------
!C
!     NOTE:  vGPSZTD_Index(numGPSZTD) is initialized in s/r dobsgpsgb
!
      if (.not.allocated(vGPSZTD_Index)) then
         call abort3d('oda_Hgp: ERROR:  vGPSZTD_Index not allocated!')
      elseif (.not.allocated(vGPSZTD_Jacobian)) then
         write(*,*) ' Allocate vGPSZTD_Jacobian(numGPSZTD,2*NFLEV+1)'
         allocate(vGPSZTD_Jacobian(numGPSZTD,2*NFLEV+1))
         allocate(vGPSZTD_lJac(numGPSZTD))
         vGPSZTD_lJac = .false.
         vGPSZTD_Jacobian = 0.0d0
      endif

!   If first time (iteration), store the Jacobians for all ZTD data to be assimilated

!-----------------------------------------------------------------------------------------
      INIT: IF ( .not.vGPSZTD_lJac(1) ) THEN

      allocate(ZTTB(NFLEV))
      allocate(ZHUB(NFLEV))
      allocate(ZPPB(NFLEV))
      allocate(ZDP(NFLEV))
      
      write(*,*) 'oda_Hgp: Storing Jacobians for GPS ZTD data ...'
      write(*,*) '   INFO: Analysis grid iversion = ', iversion
      write(*,*) '         LSTAG = ', LSTAG
      write(*,*) '         col_getNumLev(lcolumng,TH) = ', NFLEV
      write(*,*) '         col_getNumLev(lcolumn,TH)  = ', NFLEV2
      write(*,*) '         numGPSZTD = ', numGPSZTD

      if ( NFLEV .ne. NFLEV2 ) call abort3d('oda_Hgp: ERROR: NFLEV .ne. NFLEV2!')

      icount = 0

      ! loop over all header indices of the 'GP' family (GPS observations)
      call obs_set_current_header_list(lobsSpaceData,'GP')
      HEADER_0: do
         index_header = obs_getHeaderIndex(lobsSpaceData)
         if (index_header < 0) exit HEADER_0
!C
!C     *     Scan for ZTD assimilation at this location
!C
         ASSIM = .FALSE.
         ! loop over all body indices for this index_header
         call obs_set_current_body_list(lobsSpaceData, index_header)
         BODY_0: DO 
            index_body = obs_getBodyIndex(lobsSpaceData)
            if (index_body < 0) exit BODY_0
            if (   (obs_headElem_i(lobsSpaceData,OBS_ITY,INDEX_HEADER) .eq. 189) &
             .and. (obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY) .EQ. BUFR_NEZD) &
             .and. (obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY) .EQ. 1) ) then
               ZLEV = obs_bodyElem_r(lobsSpaceData,OBS_PPP,INDEX_BODY)
               ASSIM = .TRUE.
            endif
         ENDDO BODY_0      

         IF ( ASSIM ) THEN
!C
!C     *    LR background profile at the observation location x :
!C
            icount = icount + 1
            Lat  = obs_headElem_r(lobsSpaceData,OBS_LAT,INDEX_HEADER)
            ZLAT = Lat * MPC_DEGREES_PER_RADIAN_R8
            Lon  = obs_headElem_r(lobsSpaceData,OBS_LON,INDEX_HEADER)
            ZLON = Lon * MPC_DEGREES_PER_RADIAN_R8
            ZP0B = col_getElem(lcolumng,1,INDEX_HEADER,'P0')
            DO JL = 1, NFLEV
              ZTTB(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'TT') - p_TC
              ZHUB(JL) = col_getElem(lcolumng,JL,INDEX_HEADER,'HU')
              ZPPB(JL) = col_getPressure(lcolumng,JL,INDEX_HEADER,varType)
!C            Get ZDP = dP/dP0
              ZDP(JL)  = col_getPressureDeriv(lcolumng,JL,INDEX_HEADER,varType)
            ENDDO
            if ( ZPPB(NFLEV) .ne. ZP0B ) then
              write(*,*) ' oda_Hgp: ERROR: ZPPB(NFLEV) .ne. ZP0B'
              write(*,*) '          ZPPB(NFLEV), ZP0B =', ZPPB(NFLEV), ZP0B
              call abort3d('oda_Hgp:ABORT')
            endif
            ZMT  = col_getHeight(lcolumng,NFLEV,INDEX_HEADER,'TH')/RG
            if ( icount .eq. 1 .and. LTESTOP ) write(*,*) 'ZDP (dpdp0) = ', (ZDP(JL),JL= 1,NFLEV)
!c
            CALL gpsstructztd(NFLEV,Lat,Lon,ZMT,ZPPB,ZDP,ZTTB,ZHUB,LBEVIS,IREFOPT,PRF)
            CALL gpsZTDopv(ZLEV,PRF,LBEVIS,ZDZMIN,ZTDopv,ZPSMOD,IZTDOP)
!C          Observation Jacobian H'(xb)            
            JAC = ZTDopv%DVar
            iztd = i_from_index(INDEX_HEADER)
            DO JL = 1, 2*NFLEV+1
                vGPSZTD_Jacobian(iztd,JL) = JAC(JL)
            ENDDO
            vGPSZTD_lJac(iztd) = .true.
!            
            if ( icount .le. 3 .and. LTESTOP ) then
               write(*,*) '--------------------------------------------------------- '
               write(*,*) iztd, obs_elem_c(lobsSpaceData,'STID',INDEX_HEADER),'ZTDopv (m) = ', ZTDopv%Var
               CALL gpsPW(PRF,ZPWMOD)
!           sfc pressure dx               
               ZPPB(NFLEV) = ZPPB(NFLEV) + 50.0d0
               nullify(dpdp0)
               sfcfield = ZP0B + 50.0d0
               status = vgd_dpidpis(vco_anl%vgrid,vco_anl%ip1_T,dpdp0,sfcfield)
               ZDP = dpdp0(1:NFLEV)
               CALL gpsstructztd(NFLEV,Lat,Lon,ZMT,ZPPB,ZDP,ZTTB,ZHUB,LBEVIS,IREFOPT,PRF2)
               CALL gpsZTDopv(ZLEV,PRF2,LBEVIS,ZDZMIN,ZTDopv2,ZPSMOD,IZTDOP)
               write(*,*) ' ZTD Operator Test:  dP0 = +50 Pa'
               write(*,*) ' dZTD NL     = ', ZTDopv2%Var - ZTDopv%Var
               write(*,*) ' dZTD Linear = ', vGPSZTD_Jacobian(iztd,2*NFLEV+1)*50.0d0
               write(*,*) ' '
               ZPPB(NFLEV) = ZPPB(NFLEV) - 50.0d0
!           log(q) dx 
               ZHUB(64) = ZHUB(64) - 0.44D-01
               ZHUB(65) = ZHUB(65) - 0.44D-01
               ZHUB(66) = ZHUB(66) - 0.44D-01
               CALL gpsstructztd(NFLEV,Lat,Lon,ZMT,ZPPB,ZDP,ZTTB,ZHUB,LBEVIS,IREFOPT,PRF2)
               CALL gpsZTDopv(ZLEV,PRF2,LBEVIS,ZDZMIN,ZTDopv2,ZPSMOD,IZTDOP)
               CALL gpsPW(PRF2,ZPWMOD2)
               write(*,*) ' ZTD Operator Test:  dLQ = -0.44E-01 JL = 64,65,66'
               write(*,*) ' dPW (mm)    = ', ZPWMOD2 - ZPWMOD
               write(*,*) ' dZTD NL     = ', ZTDopv2%Var - ZTDopv%Var
               dZTD = vGPSZTD_Jacobian(iztd,64+NFLEV)*(-0.44D-01) + vGPSZTD_Jacobian(iztd,65+NFLEV)*(-0.44D-01) + &
                      vGPSZTD_Jacobian(iztd,66+NFLEV)*(-0.44D-01)
               write(*,*) ' dZTD Linear = ', dZTD
               write(*,*) ' '
               ZHUB(64) = ZHUB(64) + 0.44D-01
               ZHUB(65) = ZHUB(65) + 0.44D-01
               ZHUB(66) = ZHUB(66) + 0.44D-01
!           temperature dx                   
               ZTTB(64) = ZTTB(64) + 2.0d0
               ZTTB(65) = ZTTB(65) + 2.0d0
               ZTTB(66) = ZTTB(66) + 2.0d0
               CALL gpsstructztd(NFLEV,Lat,Lon,ZMT,ZPPB,ZDP,ZTTB,ZHUB,LBEVIS,IREFOPT,PRF2)
               CALL gpsZTDopv(ZLEV,PRF2,LBEVIS,ZDZMIN,ZTDopv2,ZPSMOD,IZTDOP)
               write(*,*) ' ZTD Operator Test:  dTT = +2.0K JL = 64,65,66'
               write(*,*) ' dZTD NL     = ', ZTDopv2%Var - ZTDopv%Var
               dZTD = vGPSZTD_Jacobian(iztd,64)*2.0d0 + vGPSZTD_Jacobian(iztd,65)*2.0d0 + &
                      vGPSZTD_Jacobian(iztd,66)*2.0d0
               write(*,*) ' dZTD Linear = ', dZTD
               write(*,*) '--------------------------------------------------------- '              
            endif
         
         ENDIF
         
      ENDDO HEADER_0

      deallocate(ZTTB)
      deallocate(ZHUB)
      deallocate(ZPPB)
      deallocate(ZDP)
      
      write(*,*) 'oda_Hgp:   Number of ZTD data (icount) = ', icount
      write(*,*) '           Expected number (numGPSZTD) = ', numGPSZTD
      write(*,*) '           Last iztd                   = ', iztd
      write(*,*) '           vGPSZTD_Index(1)            = ', vGPSZTD_Index(1)
      write(*,*) '           vGPSZTD_Index(iztd)         = ', vGPSZTD_Index(iztd)
      
      if ( icount .ne. numGPSZTD ) then
        call abort3d('oda_Hgp: ERROR: icount .ne. numGPSZTD!')
      endif
      if ( icount .ne. iztd ) then
        call abort3d('oda_Hgp: ERROR: icount .ne. iztd!')
      endif      
      if ( numGPSZTD .ne. iztd ) then
        call abort3d('oda_Hgp: ERROR: numGPSZTD .ne. iztd!')
      endif


      ENDIF INIT
!-----------------------------------------------------------------------------------------

      icount = 0

      ! loop over all header indices of the 'GP' family (GPS observations)
      call obs_set_current_header_list(lobsSpaceData,'GP')
      HEADER: do
         index_header = obs_getHeaderIndex(lobsSpaceData)
         if (index_header < 0) exit HEADER
!C
!C     *     Scan for ZTD assimilation at this location
!C
         ASSIM = .FALSE.
         ! loop over all body indices for this index_header
         call obs_set_current_body_list(lobsSpaceData, index_header)
         BODY: DO 
            index_body = obs_getBodyIndex(lobsSpaceData)
            if (index_body < 0) exit BODY

            if (   (obs_headElem_i(lobsSpaceData,OBS_ITY,INDEX_HEADER) .eq. 189) &
             .and. (obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY) .EQ. BUFR_NEZD) &
             .and. (obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY) .EQ. 1) ) then
               ASSIM = .TRUE.
            ENDIF
         ENDDO BODY
!C
!C     * If ZTD assimilation, apply the TL observation operator
!C
         IF ( ASSIM ) THEN
            iztd = i_from_index(INDEX_HEADER)
            if ( iztd < 1 .or. iztd > numGPSZTD ) then
              call abort3d('oda_Hgp: ERROR: index from i_from_index() is out of range!')
            endif
!C
!C     *    Local vector state (analysis increments)
!C
            DO JL = 1, NFLEV
               DX (JL)        = col_getElem(LCOLUMN,JL,index_header,'TT')
               DX (NFLEV+JL)  = col_getElem(LCOLUMN,JL,index_header,'HU')
            ENDDO
            DX (2*NFLEV+1)    = col_getElem(LCOLUMN,1 ,index_header,'P0')

!C     *    Evaluate H'(xb)*dX
!C
            ZHX = 0.D0
            DO JL = 1, 2*NFLEV+1
                ZHX = ZHX + vGPSZTD_Jacobian(iztd,JL)*DX(JL)
            ENDDO
!C
!C     *    Store ZHX = H'dx in OBS_WORK
!C
            ! loop over all body indices for this index_header
            call obs_set_current_body_list(lobsSpaceData, index_header)
            BODY_2: DO 
               index_body = obs_getBodyIndex(lobsSpaceData)
               if (index_body < 0) exit BODY_2

               IF (   (obs_headElem_i(lobsSpaceData,OBS_ITY,INDEX_HEADER) .eq. 189) &
                .and. (obs_bodyElem_i(lobsSpaceData,OBS_VNM,INDEX_BODY) .EQ. BUFR_NEZD) &
                .and. (obs_bodyElem_i(lobsSpaceData,OBS_ASS,INDEX_BODY) .EQ. 1) ) then
                  call obs_bodySet_r(lobsSpaceData,OBS_WORK,INDEX_BODY, ZHX)
                  icount = icount + 1
                  if ( icount .le. 3 .and. LTESTOP ) then
                    write(*,*) iztd, obs_elem_c(lobsSpaceData,'STID',INDEX_HEADER)
                    write(*,*) 'JAC(ncv) = ', (vGPSZTD_Jacobian(iztd,JL),JL=1,2*NFLEV+1)
                    write(*,*) 'DTT(JL)  = ', (DX(JL),JL=1,NFLEV)
                    write(*,*) 'DHU(JL)  = ', (DX(JL),JL=NFLEV+1,2*NFLEV)
                    write(*,*) 'DP0(JL)  = ', DX(2*NFLEV+1)
                    write(*,*) 'ZHX (mm) = ', ZHX*1000.D0
                  endif
               ENDIF
            ENDDO BODY_2
         
         ENDIF ! ASSIM

      ENDDO HEADER
      
!      WRITE(*,*) 'oda_Hgp: Number of ZTD data locations with obs_bodySet_r(OBS_OMA) = ', icount

!      WRITE(*,*)'EXIT oda_Hgp'

      RETURN
      END subroutine oda_Hgp


end subroutine oda_H