! ObsSpaceData_mod:  the module, ObsSpaceData_mod, follows IndexListDepot_mod
#include "maincompileswitch.inc"
#include "compileswitches.inc"


! NOTE:  Throughout this file:
!             column_index   - is not (in general) indexed from one.  Each column
!                              index has an equivalent name, OBS_*.
!             active_index   - is indexed from one by definition (a column index)
!             row_index      - is indexed from one.  It has no equivalent name.
!             bodyIndex, etc.- necessarily a row index
!             HeaderIndex,etc.-necessarily a row index


module IndexListDepot_mod 3,2
   ! PURPOSE:
   !    The raison d'etre of this module is to support ObsSpaceData_mod in
   !    facilitating the traversal of a selection of the rows in its table.  The
   !    selection of rows could be from either the header table or the body
   !    table. ObsSpaceData_mod currently populates the list with one of:
   !                 all header members of a given family
   !                 all   body members of a given family
   !                 all   body members of a given header row index
   !
   ! USAGE:
   !    An ObsSpaceData_mod client must first call either
   !    obs_set_current_body_list or obs_set_current_header_list, specifying
   !    either the family of interest or the header row index of interest. 
   !    This does not return the list directly to the caller, but rather writes
   !    the list, as a struct_index_list, to the private contents of the obs
   !    oject that is returned to the caller but which cannot be examined by the
   !    caller.  Two lists can be active simultaneously:  one header list and one
   !    body list.
   !
   !    In order to access the indices that are in the list, the ObsSpaceData_mod
   !    client must call either obs_getHeaderIndex or obs_getBodyIndex, giving
   !    the ObsSpaceData object as the only argument.  On each call, one index is
   !    returned.  On calls after the last index in the list, a value of -1 is
   !    returned.
   !
   !    This is not a fully fledged module.  It is better described as a
   !    structure definition with a couple of helpful methods.  It is intended
   !    that the client, ObsSpaceData_mod, read/write directly from/to instances
   !    of these structures.
   !
   ! STRUCT_INDEX_LIST:
   !    A struct_index_list contains the identity of the family or header that
   !    was used to create the list, the actual list of indices, and the number
   !    of the list element that was last returned to the user.
   !
   ! STRUCT_INDEX_LIST_DEPOT:
   !    Because it is typical for a client to traverse a small group of lists
   !    several times each, performance is improved by retaining recent lists,
   !    thus avoiding having to regenerate them on each request.  Recent lists
   !    are stored in a struct_index_list_depot.  ObsSpaceData_mod contains one
   !    struct_index_list_depot for header lists and another for body lists.
   !    The struct_index_list_depot structure contains current_list, a pointer to
   !    the list in the depot that was last requested by the ObsSpaceData_mod
   !    client.
   !
   ! OMP:
   !    ObsSpaceData_mod has been designed so that it may be called from within
   !    an OMP section of code.  If there are n OMP threads, it is possible that
   !    there be as many as n lists in use simultaneously.  The parameter, 
   !    NUMBER_OF_LISTS, has been set to n to accommodate that many threads.  In
   !    this case, because the current_list of the depot is not OMP-private, it
   !    cannot be asked to remember the current list for each of the OMP threads.
   !    Therefore, obs_set_current_header/body_list returns to the client an
   !    additional pointer to the list itself.  This pointer must then be passed
   !    as an optional parameter to obs_getHeader/BodyIndex.  When a new list is
   !    requested by an OMP thread, the same physical memory is re-used for the
   !    new list.
   !
   !    In order to ensure that the same physical memory is not initially
   !    distributed to more than one OMP thread, the two small sections of
   !    IndexListDepot_mod that accomplish this are marked omp-critical.
   !
   ! author  : J.W. Blezius - 2012
   !

   implicit none
   save
   public

   ! methods
   public :: ild_initialize             ! initialize a list depot
   public :: ild_finalize               ! finalize a list depot
   public :: ild_get_empty_index_list   ! return a pointer to an initialized list
   public :: ild_get_next_index         ! return the next element in the list


   interface ild_get_next_index
      module procedure ild_get_next_index_depot
      module procedure ild_get_next_index_private
   end interface ild_get_next_index

                                        ! This dimension must accommodate the
                                        ! maximum number of OMP processors 
   integer, parameter :: NUMBER_OF_LISTS = 32

   type struct_index_list
      ! a list of integers, not to say indices into a struct_obs
      character(len=2) :: family        ! current_element's belong to this family
                                        ! Used only for a body list:
      integer :: header                 ! current_element's belong to this header
      integer :: current_element        ! the element that has just been returned

                                        ! the actual list of integers
                                        ! N.B.:  These are the indices that are
                                        !        being held for the client.
                                        !        In the context of this module,
                                        !        these are elements; i.e. row
                                        !        indices.  Current_element is the
                                        !        last index that was returned
                                        !        to the client.
      integer, dimension(:), allocatable :: indices
   end type struct_index_list

   type struct_index_list_depot
      ! A collection of lists, either empty or populated
                                        ! the collection of lists
      type(struct_index_list), dimension(NUMBER_OF_LISTS) :: index_lists
      integer :: list_last_attributed   ! list that was populated most recently
                                        ! list that was   used    most recently
      type(struct_index_list), pointer :: current_list
   end type struct_index_list_depot


contains

   subroutine ild_finalize(depot) 2
      !
      ! PURPOSE:
      !      Finalize the indicated list depot
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
                                        ! the depot to be finalized
      type(struct_index_list_depot), intent(inout) :: depot

      integer :: list                   ! an index

      ! Deallocate each list
      do list = 1,NUMBER_OF_LISTS
         depot%index_lists(list)%family = 'xx'
         depot%index_lists(list)%header = 0
         depot%index_lists(list)%current_element = 0
         deallocate(depot%index_lists(list)%indices)
      end do
      depot%list_last_attributed = 0
   end subroutine ild_finalize



   function ild_get_empty_index_list(depot, private_list) & 8
                                                         result(empty_index_list)
      !
      ! PURPOSE:
      !      From the given depot, return an index-list structure that contains
      !      no data, as a pointer.
      !
      !      In other words, clear data from the (cyclicly) next (i.e. oldest)
      !      list and return a pointer to it.
      !
      !      If the list is being used within an OMP block, the ObsSpaceData
      !      client is responsible for holding a pointer to its own list.  This
      !      is supplied as the parameter, private_list.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
                                        ! the returned list
      type(struct_index_list), pointer :: empty_index_list
                                        ! the depot containing the list
      type(struct_index_list_depot), intent(inout), target :: depot
                                        ! used instead of depot (for OMP blocks)
      type(struct_index_list), pointer, intent(inout), optional :: private_list

      nullify(empty_index_list)

      if(present(private_list)) then
         ! This is an OMP thread
         if(associated(private_list)) then
            ! Memory has already been assigned for that thread.  Re-use it.
            empty_index_list => private_list ! Set the return pointer
         end if
      end if

      if(.not. associated(empty_index_list)) then
!$omp critical
         ! Increment (cyclicly) the index to the next list
         depot%list_last_attributed = depot%list_last_attributed + 1
         if (depot%list_last_attributed > NUMBER_OF_LISTS) &
            depot%list_last_attributed = 1

         ! Set the return pointer
         empty_index_list => depot%index_lists(depot%list_last_attributed)
!$omp end critical
      end if

      ! Initialize some values in the list
      ! empty_index_list%indices(:) = -1 --> No, the array is too big.
      empty_index_list%family = '  '
      empty_index_list%header = -1
      empty_index_list%current_element = 0

      return
   end function ild_get_empty_index_list



   function ild_get_next_index_depot(depot, no_advance) result(next_index) 1
      !
      ! PURPOSE:
      !      From the given depot, increment the index to the current element,
      !      and return the element itself, the new current element.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      integer :: next_index             ! the returned index

                                        ! the depot containing the list
      type(struct_index_list_depot), intent(inout), target :: depot
                                        ! if present, do not increment
                                        ! current_element, just return next one
      logical, intent(in), optional :: no_advance

                                        ! current list of the depot
      type(struct_index_list), pointer :: current_list
      integer :: next_element           ! next element of the current list

      current_list => depot%current_list
!$omp critical
                                        ! Obtain the next element from the list
      next_element = current_list%current_element + 1
      next_index = current_list%indices(next_element)
      if(.not. present(no_advance) .and. next_index .ne. -1) then
                                        ! Increment the current element
         current_list%current_element = next_element
      end if
!$omp end critical
   end function ild_get_next_index_depot



   function ild_get_next_index_private(private_list, no_advance) & 1
                                                               result(next_index)
      !
      ! PURPOSE:
      !      From the given list, increment the index to the current element, and
      !      return the element itself, the new current element.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      integer :: next_index             ! the returned index

                                        ! the list of interest
      type(struct_index_list), pointer, intent(inout) :: private_list
                                        ! if present, do not increment
                                        ! current_element, just return next one
      logical, intent(in), optional :: no_advance

      integer :: next_element           ! next element of the list

                                        ! Obtain the next element from the list
      next_element = private_list%current_element + 1
      next_index = private_list%indices(next_element)

      if(.not. present(no_advance) .and. next_index .ne. -1) then
                                        ! Increment the current element
         private_list%current_element = next_element
      end if
   end function ild_get_next_index_private



   subroutine ild_initialize(depot, numHeaderBody_max) 2
      !
      ! PURPOSE:
      !      Initialize the indicated list depot
      !      NOTE:  indices is allocated with 2 extra elements to make room for
      !             the end-of-list flag that is set in
      !             obs_set_current_header/body_list
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
                                        ! the depot to be initialized
      type(struct_index_list_depot), intent(inout) :: depot
                                        ! max size of header or body of
                                        ! struct_obs & hence of depot
      integer, intent(in) :: numHeaderBody_max

      integer :: list                   ! an index

      ! Allocate each list
      do list = 1,NUMBER_OF_LISTS
         depot%index_lists(list)%family = 'xx'
         depot%index_lists(list)%header = 0
         depot%index_lists(list)%current_element = 0
         allocate(depot%index_lists(list)%indices(numHeaderBody_max+2))
         depot%index_lists(list)%indices(:)=0
      end do
      depot%list_last_attributed = 0
      nullify(depot%current_list)
   end subroutine ild_initialize

end module IndexListDepot_mod




module ObsColumnNames_mod 2
   ! NOTE:  This module is logistically a part of the ObsSpaceData_mod module.
   !        In fact, if fortran allowed it, ObsColumnNames_mod would be
   !        'contain'ed inside the ObsSpaceData_mod module.  For this reason, and
   !        more importantly because these parameters constitute a part of the
   !        visible (from outside ObsSpaceData_mod) interface to
   !        ObsSpaceData_mod, the parameters defined in this module carry the
   !        prefix, OBS_, and not CN_.
   public


   !
   ! INTEGER-HEADER COLUMN NUMBERS
   !  
   ! the first column index for integer header variables defined below
   ! (chosen such that every column number is unique, so that a mismatch between
   !  a column number and a column type (real, int, head, body) can be detected)
   integer, parameter :: NHDR_INT_BEG = 101
   integer, parameter, public :: OBS_RLN = NHDR_INT_BEG ! report location
                                             ! unique(within obsdat), possibly
   integer, parameter, public :: OBS_ONM = OBS_RLN+1 ! ordered, station id number
   integer, parameter, public :: OBS_INS = OBS_ONM+1 ! instrument ID  
   integer, parameter, public :: OBS_OTP = OBS_INS+1 ! observation Type (file index)
   integer, parameter, public :: OBS_ITY = OBS_OTP+1 ! code: instrument & retrieval type
   integer, parameter, public :: OBS_SAT = OBS_ITY+1 ! satellite code 
   integer, parameter, public :: OBS_TEC = OBS_SAT+1 ! satellite processing technique
   integer, parameter, public :: OBS_DAT = OBS_TEC+1 ! observation date YYYYMMD
   integer, parameter, public :: OBS_ETM = OBS_DAT+1 ! observation time HHMM
   integer, parameter, public :: OBS_NLV = OBS_ETM+1 ! number of data at this location
   integer, parameter, public :: OBS_OFL = OBS_NLV+1 ! report status events
   integer, parameter, public :: OBS_PAS = OBS_OFL+1 ! batch no. in sequential analysis
   integer, parameter, public :: OBS_REG = OBS_PAS+1 ! region number in the batch
   integer, parameter, public :: OBS_IP  = OBS_REG+1 ! number of mpi processors
   integer, parameter, public :: OBS_AZA = OBS_IP +1 ! satellite azimuthal angle
   integer, parameter, public :: OBS_SZA = OBS_AZA+1 ! satellite zenith angle
   integer, parameter, public :: OBS_SUN = OBS_SZA+1 ! sun zenith angle
   integer, parameter, public :: OBS_CLF = OBS_SUN+1 ! cloud fraction
   integer, parameter, public :: OBS_ST1 = OBS_CLF+1 ! header level status/rejection flag
   integer, parameter, public :: OBS_IDO = OBS_ST1+1 ! (absolutely) unique station id no.
   integer, parameter, public :: OBS_IDF = OBS_IDO+1 ! id. no. of observation-source file
   integer, parameter, public :: OBS_SAZ = OBS_IDF+1 ! sun azimuth angle
   integer, parameter, public :: OBS_GQF = OBS_SAZ+1 ! iasi GQISFLAGQUAL
   integer, parameter, public :: OBS_GQL = OBS_GQF+1 ! iasi GQISQUALINDEXLOC
   integer, parameter, public :: OBS_CF1 = OBS_GQL+1 ! AVHRR fraction of class 1
   integer, parameter, public :: OBS_CF2 = OBS_CF1+1 ! AVHRR fraction of class 2
   integer, parameter, public :: OBS_CF3 = OBS_CF2+1 ! AVHRR fraction of class 3
   integer, parameter, public :: OBS_CF4 = OBS_CF3+1 ! AVHRR fraction of class 4
   integer, parameter, public :: OBS_CF5 = OBS_CF4+1 ! AVHRR fraction of class 5
   integer, parameter, public :: OBS_CF6 = OBS_CF5+1 ! AVHRR fraction of class 6
   integer, parameter, public :: OBS_CF7 = OBS_CF6+1 ! AVHRR fraction of class 7
   integer, parameter, public :: OBS_NCO2= OBS_CF7+1 ! NCO2: number of valid CO2 slicing estimates (AIRS,IASI,CrIS)
   integer, parameter, public :: OBS_STYP= OBS_NCO2+1! surface type in obs file (0,1,2)
   integer, parameter, public :: OBS_ROQF= OBS_STYP+1! QUALITY FLAGS FOR RADIO OCCULTATION DATA


   ! the last column index for integer header variables defined just above
   integer, parameter :: NHDR_INT_END = OBS_ROQF
   integer, parameter :: NHDR_INT_SIZE = NHDR_INT_END - NHDR_INT_BEG + 1

   !
   ! INTEGER-HEADER COLUMN NAMES
   !  
   character(len=4), target :: ocn_ColumnNameList_IH(NHDR_INT_BEG:NHDR_INT_END) = &
      (/ 'RLN ','ONM ','INS ','OTP ','ITY ','SAT ','TEC ','DAT ','ETM ', &  
         'NLV ','OFL ','PAS ','REG ','IP  ','AZA ','SZA ','SUN ','CLF ', &  
         'ST1 ','IDO ','IDF ','SAZ ','GQF ','GQL ','CF1 ','CF2 ','CF3 ', &
         'CF4 ','CF5 ','CF6 ','CF7 ','NCO2','STYP','ROQF' /)  

   !
   ! REAL-HEADER COLUMN NUMBERS
   !
   ! the first column index for real header variables defined below
   ! (chosen such that every column number is unique, so that a mismatch between
   !  a column number and a column type (real, int, head, body) can be detected)
   integer, parameter :: NHDR_REAL_BEG = 201
   integer, parameter, public :: OBS_LAT = NHDR_REAL_BEG  ! latitude  in radians (N positive)
   integer, parameter, public :: OBS_LON = OBS_LAT+1 ! longitude in radians (E positive)
   integer, parameter, public :: OBS_ALT = OBS_LON+1 ! station altitude
   integer, parameter, public :: OBS_BX  = OBS_ALT+1 ! x-coordinate of block in R3
   integer, parameter, public :: OBS_BY  = OBS_BX +1 ! y-coordinate of block in R3
   integer, parameter, public :: OBS_BZ  = OBS_BY +1 ! z-coordinate of block in R3

   integer, parameter, public :: OBS_M1C1  = OBS_BZ +1   ! mean for class 1 AVHRR channel 1
   integer, parameter, public :: OBS_M1C2  = OBS_M1C1 +1 ! mean for class 1 AVHRR channel 2
   integer, parameter, public :: OBS_M1C3  = OBS_M1C2 +1 ! mean for class 1 AVHRR channel 3
   integer, parameter, public :: OBS_M1C4  = OBS_M1C3 +1 ! mean for class 1 AVHRR channel 4
   integer, parameter, public :: OBS_M1C5  = OBS_M1C4 +1 ! mean for class 1 AVHRR channel 5
   integer, parameter, public :: OBS_M1C6  = OBS_M1C5 +1 ! mean for class 1 AVHRR channel 6

   integer, parameter, public :: OBS_M2C1  = OBS_M1C6 +1 ! mean for class 2 AVHRR channel 1
   integer, parameter, public :: OBS_M2C2  = OBS_M2C1 +1 ! mean for class 2 AVHRR channel 2
   integer, parameter, public :: OBS_M2C3  = OBS_M2C2 +1 ! mean for class 2 AVHRR channel 3
   integer, parameter, public :: OBS_M2C4  = OBS_M2C3 +1 ! mean for class 2 AVHRR channel 4
   integer, parameter, public :: OBS_M2C5  = OBS_M2C4 +1 ! mean for class 2 AVHRR channel 5
   integer, parameter, public :: OBS_M2C6  = OBS_M2C5 +1 ! mean for class 2 AVHRR channel 6

   integer, parameter, public :: OBS_M3C1  = OBS_M2C6 +1 ! mean for class 3 AVHRR channel 1
   integer, parameter, public :: OBS_M3C2  = OBS_M3C1 +1 ! mean for class 3 AVHRR channel 2
   integer, parameter, public :: OBS_M3C3  = OBS_M3C2 +1 ! mean for class 3 AVHRR channel 3
   integer, parameter, public :: OBS_M3C4  = OBS_M3C3 +1 ! mean for class 3 AVHRR channel 4
   integer, parameter, public :: OBS_M3C5  = OBS_M3C4 +1 ! mean for class 3 AVHRR channel 5
   integer, parameter, public :: OBS_M3C6  = OBS_M3C5 +1 ! mean for class 3 AVHRR channel 6

   integer, parameter, public :: OBS_M4C1  = OBS_M3C6 +1 ! mean for class 4 AVHRR channel 1
   integer, parameter, public :: OBS_M4C2  = OBS_M4C1 +1 ! mean for class 4 AVHRR channel 2
   integer, parameter, public :: OBS_M4C3  = OBS_M4C2 +1 ! mean for class 4 AVHRR channel 3
   integer, parameter, public :: OBS_M4C4  = OBS_M4C3 +1 ! mean for class 4 AVHRR channel 4
   integer, parameter, public :: OBS_M4C5  = OBS_M4C4 +1 ! mean for class 4 AVHRR channel 5
   integer, parameter, public :: OBS_M4C6  = OBS_M4C5 +1 ! mean for class 4 AVHRR channel 6

   integer, parameter, public :: OBS_M5C1  = OBS_M4C6 +1 ! mean for class 5 AVHRR channel 1
   integer, parameter, public :: OBS_M5C2  = OBS_M5C1 +1 ! mean for class 5 AVHRR channel 2
   integer, parameter, public :: OBS_M5C3  = OBS_M5C2 +1 ! mean for class 5 AVHRR channel 3
   integer, parameter, public :: OBS_M5C4  = OBS_M5C3 +1 ! mean for class 5 AVHRR channel 4
   integer, parameter, public :: OBS_M5C5  = OBS_M5C4 +1 ! mean for class 5 AVHRR channel 5
   integer, parameter, public :: OBS_M5C6  = OBS_M5C5 +1 ! mean for class 5 AVHRR channel 6

   integer, parameter, public :: OBS_M6C1  = OBS_M5C6 +1 ! mean for class 6 AVHRR channel 1
   integer, parameter, public :: OBS_M6C2  = OBS_M6C1 +1 ! mean for class 6 AVHRR channel 2
   integer, parameter, public :: OBS_M6C3  = OBS_M6C2 +1 ! mean for class 6 AVHRR channel 3
   integer, parameter, public :: OBS_M6C4  = OBS_M6C3 +1 ! mean for class 6 AVHRR channel 4
   integer, parameter, public :: OBS_M6C5  = OBS_M6C4 +1 ! mean for class 6 AVHRR channel 5
   integer, parameter, public :: OBS_M6C6  = OBS_M6C5 +1 ! mean for class 6 AVHRR channel 6

   integer, parameter, public :: OBS_M7C1  = OBS_M6C6 +1 ! mean for class 7 AVHRR channel 1
   integer, parameter, public :: OBS_M7C2  = OBS_M7C1 +1 ! mean for class 7 AVHRR channel 2
   integer, parameter, public :: OBS_M7C3  = OBS_M7C2 +1 ! mean for class 7 AVHRR channel 3
   integer, parameter, public :: OBS_M7C4  = OBS_M7C3 +1 ! mean for class 7 AVHRR channel 4
   integer, parameter, public :: OBS_M7C5  = OBS_M7C4 +1 ! mean for class 7 AVHRR channel 5
   integer, parameter, public :: OBS_M7C6  = OBS_M7C5 +1 ! mean for class 7 AVHRR channel 6


   integer, parameter, public :: OBS_S1C1  = OBS_M7C6 +1 ! stdev for class 1 AVHRR channel 1
   integer, parameter, public :: OBS_S1C2  = OBS_S1C1 +1 ! stdev for class 1 AVHRR channel 2
   integer, parameter, public :: OBS_S1C3  = OBS_S1C2 +1 ! stdev for class 1 AVHRR channel 3
   integer, parameter, public :: OBS_S1C4  = OBS_S1C3 +1 ! stdev for class 1 AVHRR channel 4
   integer, parameter, public :: OBS_S1C5  = OBS_S1C4 +1 ! stdev for class 1 AVHRR channel 5
   integer, parameter, public :: OBS_S1C6  = OBS_S1C5 +1 ! stdev for class 1 AVHRR channel 6

   integer, parameter, public :: OBS_S2C1  = OBS_S1C6 +1 ! stdev for class 2 AVHRR channel 1
   integer, parameter, public :: OBS_S2C2  = OBS_S2C1 +1 ! stdev for class 2 AVHRR channel 2
   integer, parameter, public :: OBS_S2C3  = OBS_S2C2 +1 ! stdev for class 2 AVHRR channel 3
   integer, parameter, public :: OBS_S2C4  = OBS_S2C3 +1 ! stdev for class 2 AVHRR channel 4
   integer, parameter, public :: OBS_S2C5  = OBS_S2C4 +1 ! stdev for class 2 AVHRR channel 5
   integer, parameter, public :: OBS_S2C6  = OBS_S2C5 +1 ! stdev for class 2 AVHRR channel 6

   integer, parameter, public :: OBS_S3C1  = OBS_S2C6 +1 ! stdev for class 3 AVHRR channel 1
   integer, parameter, public :: OBS_S3C2  = OBS_S3C1 +1 ! stdev for class 3 AVHRR channel 2
   integer, parameter, public :: OBS_S3C3  = OBS_S3C2 +1 ! stdev for class 3 AVHRR channel 3
   integer, parameter, public :: OBS_S3C4  = OBS_S3C3 +1 ! stdev for class 3 AVHRR channel 4
   integer, parameter, public :: OBS_S3C5  = OBS_S3C4 +1 ! stdev for class 3 AVHRR channel 5
   integer, parameter, public :: OBS_S3C6  = OBS_S3C5 +1 ! stdev for class 3 AVHRR channel 6

   integer, parameter, public :: OBS_S4C1  = OBS_S3C6 +1 ! stdev for class 4 AVHRR channel 1
   integer, parameter, public :: OBS_S4C2  = OBS_S4C1 +1 ! stdev for class 4 AVHRR channel 2
   integer, parameter, public :: OBS_S4C3  = OBS_S4C2 +1 ! stdev for class 4 AVHRR channel 3
   integer, parameter, public :: OBS_S4C4  = OBS_S4C3 +1 ! stdev for class 4 AVHRR channel 4
   integer, parameter, public :: OBS_S4C5  = OBS_S4C4 +1 ! stdev for class 4 AVHRR channel 5
   integer, parameter, public :: OBS_S4C6  = OBS_S4C5 +1 ! stdev for class 4 AVHRR channel 6

   integer, parameter, public :: OBS_S5C1  = OBS_S4C6 +1 ! stdev for class 5 AVHRR channel 1
   integer, parameter, public :: OBS_S5C2  = OBS_S5C1 +1 ! stdev for class 5 AVHRR channel 2
   integer, parameter, public :: OBS_S5C3  = OBS_S5C2 +1 ! stdev for class 5 AVHRR channel 3
   integer, parameter, public :: OBS_S5C4  = OBS_S5C3 +1 ! stdev for class 5 AVHRR channel 4
   integer, parameter, public :: OBS_S5C5  = OBS_S5C4 +1 ! stdev for class 5 AVHRR channel 5
   integer, parameter, public :: OBS_S5C6  = OBS_S5C5 +1 ! stdev for class 5 AVHRR channel 6

   integer, parameter, public :: OBS_S6C1  = OBS_S5C6 +1 ! stdev for class 6 AVHRR channel 1
   integer, parameter, public :: OBS_S6C2  = OBS_S6C1 +1 ! stdev for class 6 AVHRR channel 2
   integer, parameter, public :: OBS_S6C3  = OBS_S6C2 +1 ! stdev for class 6 AVHRR channel 3
   integer, parameter, public :: OBS_S6C4  = OBS_S6C3 +1 ! stdev for class 6 AVHRR channel 4
   integer, parameter, public :: OBS_S6C5  = OBS_S6C4 +1 ! stdev for class 6 AVHRR channel 5
   integer, parameter, public :: OBS_S6C6  = OBS_S6C5 +1 ! stdev for class 6 AVHRR channel 6

   integer, parameter, public :: OBS_S7C1  = OBS_S6C6 +1 ! stdev for class 7 AVHRR channel 1
   integer, parameter, public :: OBS_S7C2  = OBS_S7C1 +1 ! stdev for class 7 AVHRR channel 2
   integer, parameter, public :: OBS_S7C3  = OBS_S7C2 +1 ! stdev for class 7 AVHRR channel 3
   integer, parameter, public :: OBS_S7C4  = OBS_S7C3 +1 ! stdev for class 7 AVHRR channel 4
   integer, parameter, public :: OBS_S7C5  = OBS_S7C4 +1 ! stdev for class 7 AVHRR channel 5
   integer, parameter, public :: OBS_S7C6  = OBS_S7C5 +1 ! stdev for class 7 AVHRR channel 6
   integer, parameter, public :: OBS_ETOP  = OBS_S7C6 +1 ! CO2 slicing consensus (median) cloud top pressure
   integer, parameter, public :: OBS_VTOP  = OBS_ETOP +1 ! estimated error on CO2 slicing cloud top pressure
   integer, parameter, public :: OBS_ECF   = OBS_VTOP +1 ! CO2 slicing effective cloud fraction 
   integer, parameter, public :: OBS_VCF   = OBS_ECF  +1 ! estimated error on CO2 CO2 slicing cloud  fraction 
   integer, parameter, public :: OBS_HE    = OBS_VCF  +1 ! cloud effective height (one channel)
   integer, parameter, public :: OBS_ZTSR  = OBS_HE   +1 ! retrieved skin temperature from window channel in K
   integer, parameter, public :: OBS_ZTM   = OBS_ZTSR +1 ! model temperature, eta=1, in K (should not be there)
   integer, parameter, public :: OBS_ZTGM  = OBS_ZTM  +1 ! surface model temperature (skin) in K
   integer, parameter, public :: OBS_ZLQM  = OBS_ZTGM +1 ! specific humidity at surface (2m) in kg/kg
   integer, parameter, public :: OBS_ZPS   = OBS_ZLQM +1 ! surface model pressure in Pa
   integer, parameter, public :: OBS_TRAD  = OBS_ZPS  +1 ! Local EARTH Radius Metres
   integer, parameter, public :: OBS_GEOI  = OBS_TRAD +1 ! Geoid Undulation  Metres

   ! the last column index for real header variables defined just above
   integer, parameter :: NHDR_REAL_END = OBS_GEOI
   integer, parameter :: NHDR_REAL_SIZE = NHDR_REAL_END - NHDR_REAL_BEG + 1

   !
   ! REAL-HEADER COLUMN NAMES
   !
   character(len=4), target :: ocn_ColumnNameList_RH(NHDR_REAL_BEG:NHDR_REAL_END) =  &
      (/'LAT ','LON ','ALT ','BX  ','BY  ','BZ  ', &
        'M1C1','M1C2','M1C3','M1C4','M1C5','M1C6', &
        'M2C1','M2C2','M2C3','M2C4','M2C5','M2C6', &
        'M3C1','M3C2','M3C3','M3C4','M3C5','M3C6', &
        'M4C1','M4C2','M4C3','M4C4','M4C5','M4C6', &
        'M5C1','M5C2','M5C3','M5C4','M5C5','M5C6', &
        'M6C1','M6C2','M6C3','M6C4','M6C5','M6C6', &
        'M7C1','M7C2','M7C3','M7C4','M7C5','M7C6', &
        'S1C1','S1C2','S1C3','S1C4','S1C5','S1C6', &
        'S2C1','S2C2','S2C3','S2C4','S2C5','S2C6', &
        'S3C1','S3C2','S3C3','S3C4','S3C5','S3C6', &
        'S4C1','S4C2','S4C3','S4C4','S4C5','S4C6', &
        'S5C1','S5C2','S5C3','S5C4','S5C5','S5C6', &
        'S6C1','S6C2','S6C3','S6C4','S6C5','S6C6', &
        'S7C1','S7C2','S7C3','S7C4','S7C5','S7C6', &
        'ETOP','VTOP','ECF ','VCF ','HE  ','ZTSR', &
        'ZTM ','ZTGM','ZLQM','ZPS ','TRAD','GEOI' /)
   !
   ! INTEGER-BODY COLUMN NUMBERS
   !
   ! the first column index for integer body variables defined below
   ! (chosen such that every column number is unique, so that a mismatch between
   !  a column number and a column type (real, int, head, body) can be detected)
   integer, parameter :: NBDY_INT_BEG = 401
   integer, parameter, public :: OBS_VNM = NBDY_INT_BEG ! variable number
   integer, parameter, public :: OBS_FLG = OBS_VNM+1  ! flags
   integer, parameter, public :: OBS_KFA = OBS_FLG+1  ! marker for forward interp problems
   integer, parameter, public :: OBS_ASS = OBS_KFA+1  ! flag to indicate if assimilated
   integer, parameter, public :: OBS_HIND= OBS_ASS+1  ! corresponding header row index
   integer, parameter, public :: OBS_VCO = OBS_HIND+1 ! type of vertical coordinate
   integer, parameter, public :: OBS_LYR = OBS_VCO+1  ! Index of anal level above observ'n
                                             ! Flag: extrapolation necessary of
   integer, parameter, public :: OBS_XTR = OBS_LYR+1  ! anal variables to obs'n location
   integer, parameter, public :: OBS_IDD = OBS_XTR+1  ! data id. no.
   ! the last column index for integer body variables defined just above
   integer, parameter :: NBDY_INT_END = OBS_IDD
   integer, parameter :: NBDY_INT_SIZE = NBDY_INT_END - NBDY_INT_BEG + 1

   !
   ! INTEGER-BODY COLUMN NAMES
   !
   character(len=4), target :: ocn_ColumnNameList_IB(NBDY_INT_BEG:NBDY_INT_END) = &
      (/ 'VNM ','FLG ','KFA ','ASS ','HIND','VCO ','LYR ','XTR ','IDD ' /)  

   !
   ! REAL-BODY COLUMN NUMBERS
   !
   ! the first column index for real body variables defined below
   ! (chosen such that every column number is unique, so that a mismatch between
   !  a column number and a column type (real, int, head, body) can be detected)
   integer, parameter :: NBDY_REAL_BEG = 501
   integer, parameter, public :: OBS_PPP = NBDY_REAL_BEG ! pressure (vertical coordinate)
   integer, parameter, public :: OBS_SEM = OBS_PPP +1 ! surface emissivity
   integer, parameter, public :: OBS_VAR = OBS_SEM +1 ! value of the observation
   integer, parameter, public :: OBS_OMP = OBS_VAR +1 ! obs - H (trial field)
   integer, parameter, public :: OBS_OMA = OBS_OMP +1 ! obs - H (analysis)
   integer, parameter, public :: OBS_OER = OBS_OMA +1 ! sigma(obs)
   integer, parameter, public :: OBS_HPHT= OBS_OER +1 ! root of (hpht with hx scalar)
   integer, parameter, public :: OBS_ZHA = OBS_HPHT+1 ! vert coordinate for Schur product
   integer, parameter, public :: OBS_OMP6= OBS_ZHA +1 ! obs - H (6-h trial field)
   integer, parameter, public :: OBS_SIGI= OBS_OMP6+1 ! ensemble-based estimate of the innov std dev
   integer, parameter, public :: OBS_SIGO= OBS_SIGI+1 ! ensemble-based estimate of obs std dev
   integer, parameter, public :: OBS_POB = OBS_SIGO+1 ! initial value of "gamma" for variational QC 
   integer, parameter, public :: OBS_WORK= OBS_POB +1 ! temporary values
   integer, parameter, public :: OBS_PRM = OBS_WORK +1 ! (adjusted) observed value for tovs in variational assimilation
   integer, parameter, public :: OBS_JOBS= OBS_PRM +1 ! contribution to obs cost function
   integer, parameter, public :: OBS_QCV = OBS_JOBS+1 ! weight-reduction factor for var QC
   ! the number of real body variables defined just above
   integer, parameter :: NBDY_REAL_END = OBS_QCV
   integer, parameter :: NBDY_REAL_SIZE = NBDY_REAL_END - NBDY_REAL_BEG + 1

   !
   ! REAL-BODY COLUMN NAMES
   !
   character(len=4), target :: ocn_ColumnNameList_RB(NBDY_REAL_BEG:NBDY_REAL_END) = &
      (/ 'PPP ','SEM ','VAR ','OMP ','OMA ','OER ','HPHT','ZHA ','OMP6','SIGI','SIGO', &  
         'POB ','OMF ','PRM ','JOBS','QCV ' /)
end module ObsColumnNames_mod




module ObsDataColumn_mod 1,1
   ! This module is used exclusively by the obsSpaceData module which follows
   ! in this file. The derived type is used to represent a "column" of
   ! observation data in an instance of the struct_obs defined in obsSpaceData.
   ! It contains a pointer for each possible type of data stored in a column,
   ! but only one should be allocated at any time.
   !
   ! author  : Mark Buehner - 2012
   !
   use ObsColumnNames_mod
   implicit none
   save
   private

   ! CLASS-CONSTANT:
   ! CLASS-CONSTANT:
   ! CLASS-CONSTANT:

   ! This type gathers together into one structure the various CLASS-CONSTANT
   ! characteristics of a data column.  Four instances (flavours) of this derived
   ! type are defined below.
   type, public :: struct_odc_flavour
                                        ! These 2 values are informational only.
                                        ! They are used in error messages.
      character(len=4) :: dataType      ! REAL or INT
      character(len=4) :: headOrBody    ! HEAD or BODY

      integer :: ncol_beg, ncol_end
      logical         , dimension(:), pointer :: columnActive   ! indexed from 1
      character(len=4), dimension(:), pointer :: columnNameList

      integer,dimension(:), pointer ::activeIndexFromColumnIndex
      logical :: activeIndexFromColumnIndex_defined

      integer,dimension(:), pointer ::columnIndexFromActiveIndex
      logical :: columnIndexFromActiveIndex_defined
   end type struct_odc_flavour

   ! The four CLASS-CONSTANT flavours of data columns:
   !    Integer / Real  +  Body / Header
   type(struct_odc_flavour), public, target :: odc_flavour_IB, &
                                               odc_flavour_IH, &
                                               odc_flavour_RB, &
                                               odc_flavour_RH
   ! end of CLASS-CONSTANT objects
   ! end of CLASS-CONSTANT objects
   ! end of CLASS-CONSTANT objects


   ! methods
   public :: odc_allocate, odc_deallocate, odc_class_initialize
   public :: odc_activateColumn, odc_numActiveColumn
   public :: odc_columnElem, odc_columnSet
   public :: odc_columnIndexFromActiveIndex, odc_activeIndexFromColumnIndex

   ! This type allows a single derived type to contain either a real or an int
   type, public :: struct_obsDataColumn
      logical          :: allocated = .false.
                                        ! For these arrays:
                                        !   1st dim'n:  row index (element index)
      integer,       pointer :: value_i(:)     => NULL()
      real(OBS_REAL),pointer :: value_r(:)     => NULL()
      character(len=4) :: dataType
   end type struct_obsDataColumn


   ! This type contains one array of data columns.  Four of these are necessary
   ! to constitute a complete set of observation data (struct_obs).
   type, public :: struct_obsDataColumn_Array
                                        ! CLASS-CONSTANT values (1 of 4 flavours)
      type(struct_odc_flavour), pointer :: odc_flavour => NULL()
                                        ! object-specific values
                                        !   1st dim'n: column index (column name)
      type(struct_obsDataColumn), dimension(:), pointer :: columns => NULL()
   end type struct_obsDataColumn_Array
  

   ! These arrays store the status of the columns.  An active column is 
   ! allocated (and can therefore be used) in any object that is instantiated.
   logical, target :: columnActive_IH(NHDR_INT_BEG:NHDR_INT_END ) = .false.
   logical, target :: columnActive_RH(NHDR_REAL_BEG:NHDR_REAL_END) = .false.
   logical, target :: columnActive_IB(NBDY_INT_BEG:NBDY_INT_END ) = .false.
   logical, target :: columnActive_RB(NBDY_REAL_BEG:NBDY_REAL_END) = .false.

   integer, target :: activeIndexFromColumnIndex_IB(NBDY_INT_BEG:NBDY_INT_END)
   integer, target :: activeIndexFromColumnIndex_IH(NHDR_INT_BEG:NHDR_INT_END)
   integer, target :: activeIndexFromColumnIndex_RB(NBDY_REAL_BEG:NBDY_REAL_END)
   integer, target :: activeIndexFromColumnIndex_RH(NHDR_REAL_BEG:NHDR_REAL_END)

   integer, target :: columnIndexFromActiveIndex_IB(NBDY_INT_SIZE)
   integer, target :: columnIndexFromActiveIndex_IH(NHDR_INT_SIZE)
   integer, target :: columnIndexFromActiveIndex_RB(NBDY_REAL_SIZE)
   integer, target :: columnIndexFromActiveIndex_RH(NHDR_REAL_SIZE)

contains


   subroutine odc_abort(cdmessage) 10
      ! s/r ODC_ABORT  - Abort a job on error (same as OBS_ABORT)
      !
      !
      !Author  : P. Gauthier *ARMA/AES  June 9, 1992
      !
      !Arguments
      !     i     CDMESSAGE: message to be printed
      !
      !NOTE:  For debugging (i.e. UNIT_TESTING is defined), obs_abort should
      !       generally be followed by a 'return' in the calling routine.

#if defined(UNIT_TESTING)
      use pFUnit
#endif
      implicit none
      character(len=*), intent(in) :: cdmessage

      write(*,'(//,4X,"ABORTING IN ObsDataColumn_mod:-------",/,8X,A)')cdmessage
      call flush(6)

#if defined(UNIT_TESTING)
      call throw(Exception('exiting in odc_abort:' // cdmessage))
#else
      call qqexit(1)
      stop
#endif
   end subroutine odc_abort



   function odc_activeIndexFromColumnIndex(odc_flavour,column_index_in, & 8,1
                                           recompute) result(active_index_out)
      !
      ! PURPOSE:
      !      The list of active columns is only a subset of all possible
      !      columns.  Return the index into the list of active columns, given
      !      the index into the list of all columns.
      !
      ! author  : Mark Buehner - 2012
      !
      implicit none
      type(struct_odc_flavour), intent(inout) :: odc_flavour
      integer                 , intent(in)    :: column_index_in
      logical, optional       , intent(in)    :: recompute
      integer                                 :: active_index_out

      integer :: active_index, &
                 column_index
      character(len=100) :: message

      if(present(recompute)) then
         if(recompute) odc_flavour%activeIndexFromColumnIndex_defined=.false.
      endif

      if(.not. odc_flavour%activeIndexFromColumnIndex_defined) then
         odc_flavour%activeIndexFromColumnIndex_defined=.true.
         active_index=0
         odc_flavour%activeIndexFromColumnIndex(:)=-1

         do column_index = odc_flavour%ncol_beg, odc_flavour%ncol_end
            if(odc_flavour%columnActive(column_index)) then
               active_index=active_index+1
               odc_flavour%activeIndexFromColumnIndex(column_index) =active_index
            endif
         enddo
      endif

      active_index_out=odc_flavour%activeIndexFromColumnIndex(column_index_in)

      if(active_index_out.eq.-1) then
         write(message,*)'ODC_activeIndexFromColumnIndex: requested column is ',&
                         'not active!  Column name is ', &
                         odc_flavour%columnNameList(column_index_in)
         call odc_abort(message)
      end if
   end function odc_activeIndexFromColumnIndex



   subroutine odc_allocate(odc,numRows,name,dataType,scratchReal,scratchInt) 4,2
      ! s/r ODC_ALLOCATE  - Allocate a single column of obs data according to 
      !                     specifications in input arguments
      !
      !Arguments
      !     i/o     ODC: instance of the obsDataColumn type
      !     i       numRows: number of column rows to allocate
      !     i       name: character string name of column
      !     i       dataType: character string type of column data: REAL or INT
      !     i       headOrBody: character string indicating HEAD or BODY
      !
      ! author : M. Buehner September 27, 2012
      !
      implicit none
      type(struct_obsDataColumn), intent(inout) :: odc
      integer, intent(in) :: numRows
      character(len=*), intent(in) :: name,dataType
      real(OBS_REAL), pointer, intent(in) :: scratchReal(:)
      integer       , pointer, intent(in) :: scratchInt(:)

      if(odc%allocated) then
         call odc_abort('ODC_ALLOCATE: column is already allocated. name=' &
                        // name)
         return
      endif

      odc%allocated=.true.
      odc%dataType = dataType

      select case (trim(dataType))
      case ('INT')
         allocate(odc%value_i(numRows))
         odc%value_i(:)=0
         odc%value_r   => scratchReal

      case ('REAL')
         allocate(odc%value_r(numRows))
         odc%value_r(:)=real(0.0D0, OBS_REAL)
         odc%value_i   => scratchInt

      case default
         call odc_abort('ODC_ALLOCATE: unknown data type. type=' // dataType)
      end select

   end subroutine odc_allocate



   subroutine odc_activateColumn(odc_flavour, column_index) 12,2
      !
      ! PURPOSE:
      !      Set the 'active' flag for the indicated column.  This enables memory
      !      allocation for this column without actually allocating the memory.
      !
      ! author  : Mark Buehner - 2012
      !
      implicit none
      type(struct_odc_flavour), intent(inout)  :: odc_flavour
      integer, intent(in) :: column_index

      integer :: active_index, dummy_index

      if(.not.odc_flavour%columnActive(column_index)) then
         odc_flavour%columnActive(column_index) = .true.
      endif

      ! force the recalculation of indices to go between activeColumnIndex and
      ! columnIndex
      active_index=odc_activeIndexFromColumnIndex(odc_flavour,column_index, &
                                                  recompute=.true.)
      dummy_index =odc_columnIndexFromActiveIndex(odc_flavour,active_index, &
                                                  recompute=.true.)
   end subroutine odc_activateColumn



   subroutine odc_initColumnFlavour(odc_flavour, dataType_in, headOrBody_in) 4
      !
      ! PURPOSE:  Set pointers according to the four column flavours (header /
      !           body, integer / real).
      !      
      ! author  : J.W. Blezius - 2013
      !
      type(struct_odc_flavour), intent(inout) :: odc_flavour
      character(len=*)        , intent(in)    :: dataType_in    ! REAL or INT
      character(len=*)        , intent(in)    :: headOrBody_in  ! HEAD or BODY

      odc_flavour%dataType   = trim(dataType_in)
      odc_flavour%headOrBody = trim(headOrBody_in)

      select case (trim(dataType_in))
      case ('REAL')
         select case (trim(headOrBody_in))
         case ('HEAD')
            odc_flavour%ncol_beg = NHDR_REAL_BEG
            odc_flavour%ncol_end = NHDR_REAL_END
            odc_flavour%columnActive   => columnActive_RH
            odc_flavour%columnNameList => ocn_ColumnNameList_RH
            odc_flavour%activeIndexFromColumnIndex=>activeIndexFromColumnIndex_RH
            odc_flavour%activeIndexFromColumnIndex_defined = .false.
            odc_flavour%columnIndexFromActiveIndex=>columnIndexFromActiveIndex_RH
            odc_flavour%columnIndexFromActiveIndex_defined = .false.
         case ('BODY')
            odc_flavour%ncol_beg = NBDY_REAL_BEG
            odc_flavour%ncol_end = NBDY_REAL_END
            odc_flavour%columnActive   => columnActive_RB
            odc_flavour%columnNameList => ocn_ColumnNameList_RB
            odc_flavour%activeIndexFromColumnIndex=>activeIndexFromColumnIndex_RB
            odc_flavour%activeIndexFromColumnIndex_defined = .false.
            odc_flavour%columnIndexFromActiveIndex=>columnIndexFromActiveIndex_RB
            odc_flavour%columnIndexFromActiveIndex_defined = .false.
         end select

      case ('INT')
         select case (trim(headOrBody_in))
         case ('HEAD')
            odc_flavour%ncol_beg = NHDR_INT_BEG
            odc_flavour%ncol_end = NHDR_INT_END
            odc_flavour%columnActive   => columnActive_IH
            odc_flavour%columnNameList => ocn_ColumnNameList_IH
            odc_flavour%activeIndexFromColumnIndex=>activeIndexFromColumnIndex_IH
            odc_flavour%activeIndexFromColumnIndex_defined = .false.
            odc_flavour%columnIndexFromActiveIndex=>columnIndexFromActiveIndex_IH
            odc_flavour%columnIndexFromActiveIndex_defined = .false. 
         case ('BODY') 
            odc_flavour%ncol_beg = NBDY_INT_BEG
            odc_flavour%ncol_end = NBDY_INT_END
            odc_flavour%columnActive   => columnActive_IB
            odc_flavour%columnNameList => ocn_ColumnNameList_IB
            odc_flavour%activeIndexFromColumnIndex=>activeIndexFromColumnIndex_IB
            odc_flavour%activeIndexFromColumnIndex_defined = .false.
            odc_flavour%columnIndexFromActiveIndex=>columnIndexFromActiveIndex_IB
            odc_flavour%columnIndexFromActiveIndex_defined = .false.
         end select
      end select

   end subroutine odc_initColumnFlavour



   subroutine odc_class_initialize(obsColumnMode, myip) 1,16
      !s/r odc_class_initialize - Set observation-data-column class variables.
      !
      ! PURPOSE:
      !      Set variables that use the same values for all instances of the
      !      class.
      !
      ! author  : J.W. Blezius - 2013 - extracted from obs_class_initialize
      !
      implicit none
      ! mode controlling the subset of columns that are activated in all objects
      character(len=*), intent(in) :: obsColumnMode
      integer, intent(in) :: myip

      integer :: column_index, list_index, ii
      integer, parameter :: COLUMN_LIST_SIZE = 100
      integer, dimension(COLUMN_LIST_SIZE) :: hdr_int_column_list, &
                  hdr_real_column_list, bdy_int_column_list, bdy_real_column_list

      ! Initialize the four column flavours:
      call odc_initColumnFlavour(odc_flavour_IB, 'INT',  'BODY')
      call odc_initColumnFlavour(odc_flavour_IH, 'INT',  'HEAD')
      call odc_initColumnFlavour(odc_flavour_RB, 'REAL', 'BODY')
      call odc_initColumnFlavour(odc_flavour_RH, 'REAL', 'HEAD')

      COLUMN_MODE:if(trim(obsColumnMode).eq.'ALL') then
         do column_index=NHDR_INT_BEG,NHDR_INT_END
            call odc_activateColumn(odc_flavour_IH, column_index)
         enddo
         do column_index=NHDR_REAL_BEG,NHDR_REAL_END
            call odc_activateColumn(odc_flavour_RH, column_index)
         enddo
         do column_index=NBDY_INT_BEG,NBDY_INT_END
            call odc_activateColumn(odc_flavour_IB, column_index)
         enddo
         do column_index=NBDY_REAL_BEG,NBDY_REAL_END
            call odc_activateColumn(odc_flavour_RB, column_index)
         enddo

      elseif(trim(obsColumnMode).eq.'ENKF') then COLUMN_MODE

         hdr_int_column_list= &
            (/OBS_RLN, OBS_ONM, OBS_INS, OBS_OTP, OBS_ITY, OBS_SAT, OBS_TEC, &
              OBS_DAT, OBS_ETM, OBS_NLV, OBS_OFL, OBS_PAS, OBS_REG, OBS_IP,  &
              OBS_AZA, OBS_SZA, OBS_SUN, OBS_CLF, OBS_ST1, OBS_IDO, OBS_IDF, &
              OBS_SAZ, OBS_GQF, OBS_GQL, OBS_ROQF, (0,ii=26,100) /)

         hdr_real_column_list= &
            (/OBS_LAT, OBS_LON, OBS_ALT, OBS_BX,  OBS_BY,  OBS_BZ, OBS_TRAD, &
              OBS_GEOI,(0,ii=9,100)/)

         bdy_int_column_list= &
            (/OBS_VNM, OBS_FLG, OBS_ASS, OBS_HIND,OBS_VCO, OBS_LYR, OBS_IDD, &
              (0,ii=8,100) /)

         bdy_real_column_list= &
            (/OBS_PPP, OBS_SEM, OBS_VAR, OBS_OMP, OBS_OMA, OBS_OER, OBS_HPHT,&
              OBS_ZHA, OBS_OMP6, OBS_SIGI, OBS_SIGO, (0,ii=12,100) /)

         do list_index=1,COLUMN_LIST_SIZE
            column_index = hdr_int_column_list(list_index)
            if(column_index .eq. 0) exit
            call odc_activateColumn(odc_flavour_IH, column_index)
         end do

         do list_index=1,COLUMN_LIST_SIZE
            column_index = hdr_real_column_list(list_index)
            if(column_index .eq. 0) exit
            call odc_activateColumn(odc_flavour_RH, column_index)
         end do

         do list_index=1,COLUMN_LIST_SIZE
            column_index = bdy_int_column_list(list_index)
            if(column_index .eq. 0) exit
            call odc_activateColumn(odc_flavour_IB, column_index)
         end do

         do list_index=1,COLUMN_LIST_SIZE
            column_index = bdy_real_column_list(list_index)
            if(column_index .eq. 0) exit
            call odc_activateColumn(odc_flavour_RB, column_index)
         end do


      elseif(trim(obsColumnMode).eq.'VAR') then COLUMN_MODE

         do column_index=NHDR_INT_BEG,NHDR_INT_END
            if(     column_index.ne.OBS_IDF &
               .and.column_index.ne.OBS_IDO &
               .and.(column_index.lt.OBS_CF1 .or. &
                     column_index.gt.OBS_STYP) &
              )call odc_activateColumn(odc_flavour_IH, column_index)
         enddo
         do column_index=NHDR_REAL_BEG,NHDR_REAL_END
            if(     column_index.ne.OBS_BX &
               .and.column_index.ne.OBS_BY &
               .and.column_index.ne.OBS_BZ &
               .and. (column_index.lt.OBS_M1C1 .or. &
                      column_index.gt.OBS_ZPS) &
              ) call odc_activateColumn(odc_flavour_RH, column_index)
         enddo
         do column_index=NBDY_INT_BEG,NBDY_INT_END
            if(     column_index.ne.OBS_KFA &
               .and.column_index.ne.OBS_IDD &
              ) call odc_activateColumn(odc_flavour_IB, column_index)
         enddo
         do column_index=NBDY_REAL_BEG,NBDY_REAL_END
            if(      column_index.ne.OBS_OMP6 &
               .and. column_index.ne.OBS_SIGI &
               .and. column_index.ne.OBS_SIGO &
              )call odc_activateColumn(odc_flavour_RB, column_index)
         enddo

      endif COLUMN_MODE
   end subroutine odc_class_initialize



   subroutine odc_columnElem(odc_array, column_index, row_index, value_i,value_r) 4,2
      !
      ! PURPOSE:
      !      Returns the value of the row_index'th element in the column array
      !      with the indicated column_index.
      !
      !      The column array can be of any one of the four possible column-array
      !      flavours.  The flavour is selected by one of four wrappers to this
      !      method.
      !
      ! author  : J.W. Blezius - 2013 - extracted from M Buehner's obs_bodyElem_i
      !
      implicit none
      type(struct_obsDataColumn_Array), intent(in)  :: odc_array
      integer                         , intent(in)  :: column_index
      integer                         , intent(in)  :: row_index
      integer                         , intent(out) :: value_i
      real(OBS_REAL)                  , intent(out) :: value_r

      character(len=100) :: message
      
      if(      column_index.ge.odc_array%odc_flavour%ncol_beg &
         .and. column_index.le.odc_array%odc_flavour%ncol_end) then
         if(odc_array%odc_flavour%columnActive(column_index)) then
            ! Return the value (return int AND real, just to make it simple)
            value_i = odc_array%columns(column_index)%value_i(row_index)
            value_r = odc_array%columns(column_index)%value_r(row_index)
         else
            write(message,*)'abort in odc_columnElem (' &
                          // odc_array%odc_flavour%dataType //',' &
                          // odc_array%odc_flavour%headOrBody // &
                          '): column not active: ', &
                          odc_array%odc_flavour%columnNameList(column_index)
            call odc_abort(message)
         endif
      else
         write(message,*) 'abort in odc_columnElem (' &
                          // odc_array%odc_flavour%dataType //',' &
                          // odc_array%odc_flavour%headOrBody // &
                          '): column index out of range: ', column_index
         call odc_abort(message); return
      endif
   end subroutine odc_columnElem



   function odc_columnIndexFromActiveIndex(odc_flavour,active_index_in, & 47
                                           recompute) result(column_index_out)
      !
      ! PURPOSE:
      !      The list of active columns is only a subset of all possible
      !      columns.  Return the index into the list of all columns, given
      !      the index into the list of active columns, and given the column
      !      flavour.
      !
      ! author  : J.W. Blezius - 2013 - generalized from
      !                                 obs_columnIndexFromActiveIndex_*
      !
      implicit none
      type(struct_odc_flavour), intent(inout) :: odc_flavour
      integer                 , intent(in)    :: active_index_in
      logical, optional       , intent(in)    :: recompute
      integer                                 :: column_index_out

      integer :: active_index, &
                 column_index

      if(present(recompute)) then
         if(recompute) odc_flavour%columnIndexFromActiveIndex_defined=.false.
      endif

      if(.not. odc_flavour%columnIndexFromActiveIndex_defined) then
         odc_flavour%columnIndexFromActiveIndex_defined=.true.
         active_index=0

         do column_index = odc_flavour%ncol_beg, odc_flavour%ncol_end
            if(odc_flavour%columnActive(column_index)) then
               active_index=active_index+1
               odc_flavour%columnIndexFromActiveIndex(active_index) =column_index
            endif
         enddo
      endif

      column_index_out=odc_flavour%columnIndexFromActiveIndex(active_index_in)
   end function odc_columnIndexFromActiveIndex



   subroutine odc_columnSet(odc_array, column_index, row_index, & 4,4
                            value_i, value_r, numElements, numElements_max)
      !
      ! PURPOSE:
      !      Sets the value of the row_index'th element in the column array with
      !      the indicated column_index.
      !
      !      The column array can be of any one of the four possible column-array
      !      flavours.  The flavour is selected by one of four wrappers to this
      !      method.
      !
      ! author  : J.W. Blezius - 2013 - extracted from obs_bodySet_i
      !
      implicit none
      type(struct_obsDataColumn_Array), intent(inout) :: odc_array
      integer                         , intent(in)    :: column_index
      integer                         , intent(in)    :: row_index
      integer                         , intent(in)    :: value_i
      real(OBS_REAL)                  , intent(in)    :: value_r
      integer                         , intent(inout) :: numElements
      integer                         , intent(in)    :: numElements_max

      character(len=100) :: message

      ! Validate the requested row_index, and
      ! Increment the number of elements, if necessary
      if(row_index > numElements_max) then
         write(message,*)'The requested ', &
                         trim(odc_array%odc_flavour%headOrBody), ' row_index, ',&
                         row_index,', is greater than the maximum, ', &
                         numElements_max
         call odc_abort(message)

      else if(row_index > numElements+1) then
         write(message,*)'The requested ', &
                         trim(odc_array%odc_flavour%headOrBody), &
                         ' row_index, ', row_index, &
                         ', is beyond the next available index, ', &
                         numElements+1
         call odc_abort(message)

      else if(row_index == numElements+1) then
         numElements = numElements+1
      end if


      ! Validate the requested column index, and
      ! Record the value
      if(      column_index.ge.odc_array%odc_flavour%ncol_beg &
         .and. column_index.le.odc_array%odc_flavour%ncol_end) then
         if(odc_array%odc_flavour%columnActive(column_index)) then
            ! Record the value (record int AND real, just to make it simple)
            odc_array%columns(column_index)%value_i(row_index) = value_i
            odc_array%columns(column_index)%value_r(row_index) = value_r
         else
            write(message,*) 'abort in odc_columnSet (' &
                          // odc_array%odc_flavour%dataType //',' &
                          // odc_array%odc_flavour%headOrBody // &
                          '): column not active: ', &
                          odc_array%odc_flavour%columnNameList(column_index)
            call odc_abort(message)
         endif
      else
         write(message,*) 'abort in odc_columnSet (' &
                          // odc_array%odc_flavour%dataType //',' &
                          // odc_array%odc_flavour%headOrBody // &
                          '): column index out of range: ', column_index
         call odc_abort(message); return
      endif

   end subroutine odc_columnSet



   subroutine odc_deallocate(odc) 4,1
      ! s/r ODC_DEALLOCATE  - Deallocate a single column of obs data
      !
      !Arguments
      !     i/o     ODC: instance of the obsDataColumn type
      !
      ! author  : M. Buehner September 27, 2012
      !
      implicit none
      type(struct_obsDataColumn), intent(inout) :: odc

      if(.not.odc%allocated) then
         call odc_abort('ODC_DEALLOCATE: column is not already allocated.')
      endif

      odc%allocated=.false.
      if(trim(odc%dataType) .eq. 'INT' .and. associated(odc%value_i)) then
         deallocate(odc%value_i)
         nullify(odc%value_i)
         nullify(odc%value_r)    ! Dont deallocate:  this is not the only pointer
      end if

      if(trim(odc%dataType) .eq. 'REAL' .and. associated(odc%value_r)) then
         deallocate(odc%value_r)
         nullify(odc%value_r)
         nullify(odc%value_i)    ! Dont deallocate:  this is not the only pointer
      endif

   end subroutine odc_deallocate



   function odc_numActiveColumn(odc_array) result(numActiveColumn) 66
      !
      ! PURPOSE:
      !      Return the number of active columns that are contained in the given
      !      column array.
      !
      !      The column array can be of any one of the four possible column-array
      !      flavours.
      !
      ! author  : J.W. Blezius - 2013 - generalized from M Buehner's
      !                                 obs_numActiveColumn_IB
      !
      implicit none
      type(struct_obsDataColumn_Array), intent(in) :: odc_array
      integer :: numActiveColumn

      integer :: column_index

      numActiveColumn=0
      do column_index = odc_array%odc_flavour%ncol_beg, &
                        odc_array%odc_flavour%ncol_end
         if(odc_array%odc_flavour%columnActive(column_index))  &
            numActiveColumn=numActiveColumn+1
      enddo
   end function odc_numActiveColumn

end module ObsDataColumn_mod



module ObsSpaceData_mod 73,10
   use ObsColumnNames_mod
   use ObsDataColumn_mod
   use IndexListDepot_mod
   implicit none
   save
   private

   ! This module deals with operations involving the data structure that
   ! stores observational information.
   !   (this had evolved from the CMA structure, originated in work by
   !    D. Vasiljevic at ECMWF)
   !
   ! First creation of the module: February 2011 by Peter Houtekamer
   !


   ! CLASS-CONSTANT:
   ! CLASS-CONSTANT:
   ! CLASS-CONSTANT:

   logical, save :: obs_class_initialized = .false.

   ! end of CLASS-CONSTANT variables.
   ! end of CLASS-CONSTANT variables
   ! end of CLASS-CONSTANT variables.


   ! PUBLIC METHODS:
   public obs_append     ! append an obsdat object to another obsdat object
   public obs_bdy        ! fill in the ObsSpaceData body from burp(3dvar version)
   public obs_bodyElem_i ! obtain an integer body element from observation object
   public obs_bodyElem_r ! obtain a real body element from the observation object
   public obs_bodyIndex_mpiglobal ! obtain mpiglobal body row index
   public obs_bodySet_i  ! set an integer body value in the observation object
   public obs_bodySet_r  ! set a real body value in the observation object
   public obs_class_initialize ! initialize class variables: column mode
   public obs_clean      ! remove from obs data those that not to be assimilated
   public obs_columnActive_IB ! return the active status for a column (T/F)
   public obs_columnActive_IH !                 "
   public obs_columnActive_RB !                 "
   public obs_columnActive_RH !                 "
   public obs_columnIndexFromName_IB ! get the index from the name
   public obs_columnIndexFromName_IH !         "
   public obs_columnIndexFromName_RB !         "
   public obs_columnIndexFromName_RH !         "
   public obs_comm       ! communicate header and body info between mpi processes
   public obs_copy       ! copy an obsdat object
   public obs_count_headers ! count the stations and observations in the object
   public obs_elem_c     ! obtain character element from the observation object
   public obs_enkf_bdy   ! fill in the ObsSpaceData body from burp(EnKF version)
   public obs_enkf_prntbdy! print all data records associated with an observation
   public obs_enkf_prnthdr! print the header of an observation record
   public obs_expandToMpiGlobal ! restore data for the mpi-global context
   public obs_finalize   ! object clean-up
   public obs_generate_header ! fill in observation-data header, from burp files
                         ! find the index into the variable types list of the
                         ! obsdat element that contains given BUFR element number
   public obs_get_obs_index_for_bufr_element
   public obs_getBodyIndex ! obtain an element from the current body list
   public obs_getFamily  ! return the family of a datum
   public obs_getHeaderIndex ! obtain an element from the current header list
   public obs_getNchanAvhrr  ! to get the number of AVHRR channels
   public obs_getNclassAvhrr ! to get the number of AVHRR radiance classes
   public obs_headElem_i ! obtain an integer header element from the obs'n object
   public obs_headElem_r ! obtain real header element from the observation object
   public obs_headerIndex_mpiglobal ! obtain mpiglobal header row index
   public obs_headSet_i  ! set an integer header value in the observation object
   public obs_headSet_r  ! set a real header value in the observation object
   public obs_initialize ! variable initialization
   public obs_mpiLocal   ! obtain the current mpi state of the observation object
   public obs_numBody    ! returns the number of bodies recorded
   public obs_numBody_max! returns the dimensioned number of bodies
   public obs_numBody_mpiglobal ! returns mpi-global number of bodies recorded
   public obs_numHeader  ! returns the number of headers recorded
   public obs_numHeader_max ! returns the dimensioned number of headers
   public obs_numHeader_mpiglobal ! returns mpi-global number of headers recorded
   public obs_order      ! put obs data in the order required for assimilation
   public obs_print      ! obs_enkf_prnthdr & obs_enkf_prntbdy for each station
   public obs_prnt_csv   ! call obs_tosqlhdr and obs_tosqlbdy for each station
   public obs_prntbdy    ! print the body data for one header
   public obs_prnthdr    ! print the data contained in one header
   public obs_read       ! read the observation data from binary files
   public obs_readstns   ! read stations for 1 analysis pass, store in obs object
   public obs_reduceToMpiLocal ! retain only data pertinent to the mpi-local PE
   public obs_select     ! select observations in a vertical range
   public obs_set_c      ! set a character value in the observation object
   public obs_set_current_body_list   ! set a body list for a family as current
   public obs_set_current_header_list ! set a header list for a family as current
   public obs_setFamily  ! set the family of a datum
   public obs_status     ! returns the values of the object's status variables
   public obs_write      ! write the observation data to binary files
                         ! (calls obs_write_hdr, obs_write_bdy, obs_write_hx
                         !  for each station)


   interface obs_getBodyIndex
      module procedure obs_getBodyIndex_depot
      module procedure obs_getBodyIndex_private
   end interface obs_getBodyIndex


   interface obs_set_current_body_list 53
      module procedure obs_set_current_body_list_from_family
      module procedure obs_set_current_body_list_from_header
      module procedure obs_set_current_body_list_all
   end interface obs_set_current_body_list


   interface obs_set_current_header_list 38
      module procedure obs_set_current_header_list_from_family
      module procedure obs_set_current_header_list_all
   end interface

   ! PRIVATE METHODS:
   private obs_abort     ! abort a job on error
   private obs_allocate  ! array allocation
   private obs_columnIndexFromName ! get the index from the name
   private obs_deallocate! array de-allocation
   private obs_mpiDistributeIndices ! distribute header & body indices for mpi parallelization
   private obs_tosqlbdy  ! write the observation data in comma-separated format
   private obs_tosqlhdr  ! write the observation header in comma-separated format
   private obs_write_bdy ! write the observation data to binary files
   private obs_write_hdr ! write the observation header to binary files
   private obs_write_hx  ! write to binary files a station's interpolated values


   ! PARAMETERS INHERITED FROM ObsColumnNames_mod (make them public)
   !    integer-header column numbers
   public :: OBS_RLN, OBS_ONM, OBS_INS, OBS_OTP, OBS_ITY, OBS_SAT, OBS_TEC
   public :: OBS_DAT, OBS_ETM, OBS_NLV, OBS_OFL, OBS_PAS, OBS_REG, OBS_IP
   public :: OBS_AZA, OBS_SZA, OBS_SUN, OBS_CLF, OBS_ST1, OBS_IDO, OBS_IDF
   public :: OBS_SAZ, OBS_GQF, OBS_GQL
   public :: OBS_CF1, OBS_CF2, OBS_CF3, OBS_CF4, OBS_CF5, OBS_CF6, OBS_CF7
   public :: OBS_NCO2,OBS_STYP,OBS_ROQF

   !    real-header column numbers
   public :: OBS_LAT, OBS_LON, OBS_ALT, OBS_BX,  OBS_BY,  OBS_BZ
   public :: OBS_M1C1, OBS_M1C2, OBS_M1C3, OBS_M1C4, OBS_M1C5, OBS_M1C6
   public :: OBS_M2C1, OBS_M2C2, OBS_M2C3, OBS_M2C4, OBS_M2C5, OBS_M2C6
   public :: OBS_M3C1, OBS_M3C2, OBS_M3C3, OBS_M3C4, OBS_M3C5, OBS_M3C6
   public :: OBS_M4C1, OBS_M4C2, OBS_M4C3, OBS_M4C4, OBS_M4C5, OBS_M4C6
   public :: OBS_M5C1, OBS_M5C2, OBS_M5C3, OBS_M5C4, OBS_M5C5, OBS_M5C6
   public :: OBS_M6C1, OBS_M6C2, OBS_M6C3, OBS_M6C4, OBS_M6C5, OBS_M6C6
   public :: OBS_M7C1, OBS_M7C2, OBS_M7C3, OBS_M7C4, OBS_M7C5, OBS_M7C6
   public :: OBS_S1C1, OBS_S1C2, OBS_S1C3, OBS_S1C4, OBS_S1C5, OBS_S1C6
   public :: OBS_S2C1, OBS_S2C2, OBS_S2C3, OBS_S2C4, OBS_S2C5, OBS_S2C6
   public :: OBS_S3C1, OBS_S3C2, OBS_S3C3, OBS_S3C4, OBS_S3C5, OBS_S3C6
   public :: OBS_S4C1, OBS_S4C2, OBS_S4C3, OBS_S4C4, OBS_S4C5, OBS_S4C6
   public :: OBS_S5C1, OBS_S5C2, OBS_S5C3, OBS_S5C4, OBS_S5C5, OBS_S5C6
   public :: OBS_S6C1, OBS_S6C2, OBS_S6C3, OBS_S6C4, OBS_S6C5, OBS_S6C6
   public :: OBS_S7C1, OBS_S7C2, OBS_S7C3, OBS_S7C4, OBS_S7C5, OBS_S7C6
   public :: OBS_ETOP, OBS_VTOP, OBS_ECF,  OBS_VCF , OBS_HE  , OBS_ZTSR
   public :: OBS_ZTM , OBS_ZTGM, OBS_ZLQM, OBS_ZPS , OBS_TRAD, OBS_GEOI
   !    integer-body column numbers
   public :: OBS_VNM, OBS_FLG, OBS_KFA, OBS_ASS, OBS_HIND,OBS_VCO, OBS_LYR
   public :: OBS_XTR, OBS_IDD

   !    real-body column numbers
   public :: OBS_PPP, OBS_SEM, OBS_VAR, OBS_OMP, OBS_OMA, OBS_OER, OBS_HPHT
   public :: OBS_ZHA, OBS_OMP6,OBS_SIGI,OBS_SIGO,OBS_POB, OBS_WORK, OBS_PRM
   public :: OBS_JOBS,OBS_QCV


   ! OBSERVATION-SPACE FUNDAMENTAL PARAMETERS
                                        ! obs variable-types table length
   integer, public, parameter :: OBS_JPNBRELEM = 57

   ! DERIVED TYPE AND MODULE VARIABLE DECLARATIONS

                                        ! It is intended that these null values
                                        ! be used with scratchRealHeader, etc.
   real(OBS_REAL), parameter :: NULL_COLUMN_VALUE_R = real(-9.99D9, OBS_REAL)
   integer       , parameter :: NULL_COLUMN_VALUE_I = -9.99

   ! This type is the goal of the ObsSpaceData and supporting modules.  An
   ! instance of this derived type contains all information pertaining to a set
   ! of observation-space data.
   type, public :: struct_obs
      private
      logical          :: allocated = .false.
                                        ! Two internal structures for
                                        ! facilitating multiple traversals of
                                        ! subsets of the observation-space data.
      type(struct_index_list_depot) :: header_index_list_depot
      type(struct_index_list_depot) :: body_index_list_depot
                                        ! For the cstnid and cfamily arrays:
                                        !   1st dim'n:  row index
      character(len=12),  pointer, dimension(:)   :: cstnid
      character(len=2),   pointer, dimension(:)   :: cfamily
                                        ! The four arrays of data columns
      type(struct_obsDataColumn_Array) :: &
                             realHeaders, & ! real header columns
                             intHeaders,  & ! integer header columns
                             realBodies,  & ! real body columns
                             intBodies      ! integer body columns

      ! These scratch arrays are the basis for allowing the manipulation of both
      ! (real and integer) values of any obsDataColumn, without having to decide
      ! which one (real or integer) contains the sought value.  This ability
      ! simplifies the code.
      real(OBS_REAL), pointer :: scratchRealHeader(:), &
                                 scratchRealBody  (:)
      integer       , pointer :: scratchIntHeader (:), &
                                 scratchIntBody   (:)

      integer :: numHeader              ! Actual number of headers on record
      integer :: numHeader_max          ! maximum number of headers(i.e.stations)
      integer :: numBody                ! Actual total number of bodies on record
      integer :: numBody_max            ! maximum number of bodies (i.e. data)

                                        ! row indices of mpiglobal data, useful
                                        ! for transforming from mpiLocal back to
                                        ! mpiGlobal
                                        !    1st dim'n: row index, only in
                                        !               mpiLocal context
      integer, pointer, dimension(:) :: headerIndex_mpiglobal  => NULL()
      integer, pointer, dimension(:) :: bodyIndex_mpiglobal    => NULL()

      logical :: mpi_local       ! T: keep only data needed by this PE (mpilocal)
   end type struct_obs


contains



   subroutine obs_abort(cdmessage) 25
      ! s/r OBS_ABORT  - Abort a job on error
      !
      !
      !Author  : P. Gauthier *ARMA/AES  June 9, 1992
      !Revision:
      !     . P. Gauthier *ARMA/AES  January 29, 1996 
      !     . P. Koclas   CMC/CMSV   January  1997 
      !         -add call to abort
      !     . S. Pellerin ARMA/SMC   October 2000
      !         - replace call to abort for call to exit(1)
      !     . C. Charette ARMA/SMC   October 2001
      !         - replace SUTERM by SUTERMF to only close files
      !     . J. Blezius  ARMA/SMC   2012
      !         - import ABORT3D into obsspacedata_mod as OBS_ABORT
      !         - delete call to SUTERMF
      !    -------------------
      ! Purpose:
      !      To stop a job when an error occurred
      !
      !Arguments
      !     i     CDMESSAGE: message to be printed

#if defined(UNIT_TESTING)
      use pFUnit
#endif
      implicit none
      character(len=*), intent(in) :: cdmessage
      integer :: initialized

      write(*,'(//,4X,"ABORTING IN ObsSpaceData_mod:-------",/,8X,A)')cdmessage
      call flush(6)

#if defined(UNIT_TESTING)
      call throw(Exception('exiting in obs_abort'))
#else
      call qqexit(13)
      stop
#endif
   end subroutine obs_abort



   subroutine obs_allocate(obsdat, numHeader_max, numBody_max, silent) 4,7
      !s/r obs_allocate - Allocate the object's arrays.
      !
      ! PURPOSE:
      !      Allocate arrays according to the parameters, numHeader_max and
      !      numBody_max.  This is a private method.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none

      type(struct_obs), intent(inout) :: obsdat
      integer,          intent(in)    :: numHeader_max,numBody_max
      logical, optional,intent(in)    :: silent

      logical :: silent_
      integer :: column_index

      if(obsdat%allocated) then
         call obs_abort('OBS_ALLOCATE:  a second allocation of ObsSpaceData has been attempted.')
         return
      endif
      obsdat%allocated=.true.

      if(present(silent)) then
         silent_  = silent
      else
         silent_  = .false.
      end if

      if(.not. silent_) then
         write(*,*) ' DIMENSIONS OF OBSERVATION ARRAYS:'
         write(*,*) ' numHeader_max = ',numHeader_max,'  numBody_max = ', &
                    numBody_max
      end if
      obsdat%numHeader_max=numHeader_max
      obsdat%numBody_max=numBody_max

      !
      ! ALLOCATE THE ARRAYS and initialize the contents
      !
      HEADER:if(numHeader_max.gt.0) then
         allocate(obsdat%cfamily(numHeader_max))
         obsdat%cfamily(:)='XX'

         allocate(obsdat%cstnid(numHeader_max))
         obsdat%cstnid(:)='XXXXXXXXXXXX'


         allocate(obsdat%scratchRealHeader(numHeader_max))
         allocate(obsdat%scratchIntHeader (numHeader_max))
         obsdat%scratchRealHeader(:) = NULL_COLUMN_VALUE_R
         obsdat%scratchIntHeader (:) = NULL_COLUMN_VALUE_I

         allocate(obsdat%realHeaders%columns(NHDR_REAL_BEG:NHDR_REAL_END))
         obsdat%realHeaders%odc_flavour => odc_flavour_RH
         do column_index=NHDR_REAL_BEG,NHDR_REAL_END
            if(obsdat%realHeaders%odc_flavour%columnActive(column_index))  &
               call odc_allocate(obsdat%realHeaders%columns(column_index), &
                                 numHeader_max, &
                                 ocn_ColumnNameList_RH(column_index),'REAL',&
                                 obsdat%scratchRealHeader, &
                                 obsdat%scratchIntHeader)
         enddo


         allocate(obsdat%intHeaders%columns(NHDR_INT_BEG:NHDR_INT_END))
         obsdat%intHeaders%odc_flavour => odc_flavour_IH
         do column_index=NHDR_INT_BEG,NHDR_INT_END
            if(obsdat%intHeaders%odc_flavour%columnActive(column_index))  &
               call odc_allocate(obsdat%intHeaders%columns(column_index), &
                                 numHeader_max, &
                                 ocn_ColumnNameList_IH(column_index),'INT ', &
                                 obsdat%scratchRealHeader, &
                                 obsdat%scratchIntHeader)
         enddo
      endif HEADER

      BODY:if(numBody_max.gt.0) then
         allocate(obsdat%scratchRealBody(numBody_max))
         allocate(obsdat%scratchIntBody (numBody_max))
         obsdat%scratchRealBody(:) = NULL_COLUMN_VALUE_R
         obsdat%scratchIntBody (:) = NULL_COLUMN_VALUE_I

         allocate(obsdat%realBodies%columns(NBDY_REAL_BEG:NBDY_REAL_END))
         obsdat%realBodies%odc_flavour => odc_flavour_RB
         do column_index=NBDY_REAL_BEG,NBDY_REAL_END
            if(obsdat%realBodies%odc_flavour%columnActive(column_index))  &
               call odc_allocate(obsdat%realBodies%columns(column_index), &
                                 numBody_max, &
                                 ocn_ColumnNameList_RB(column_index),'REAL', &
                                 obsdat%scratchRealBody, obsdat%scratchIntBody)
         enddo


         allocate(obsdat%intBodies%columns(NBDY_INT_BEG:NBDY_INT_END))
         obsdat%intBodies%odc_flavour => odc_flavour_IB
         do column_index=NBDY_INT_BEG,NBDY_INT_END
            if(obsdat%intBodies%odc_flavour%columnActive(column_index))  &
               call odc_allocate(obsdat%intBodies%columns(column_index), &
                                 numBody_max, &
                                 ocn_ColumnNameList_IB(column_index),'INT ', &
                                 obsdat%scratchRealBody, obsdat%scratchIntBody)
         enddo
      endif BODY


      call ild_initialize(obsdat%header_index_list_depot, obsdat%numHeader_max)
      call ild_initialize(obsdat%body_index_list_depot,   obsdat%numBody_max)
   end subroutine obs_allocate



   subroutine obs_append(obsdat,hx,obs_out,hx_out),20
      !
      ! PURPOSE:
      !      with a call of type obs_append(obs_1,obs_2) append obs_1 to obs_2
      !
      ! author  : Peter Houtekamer: May 2011

      type (struct_obs), intent(in)    :: obsdat
      type (struct_obs), intent(inout) :: obs_out

      real(8),           intent(in)    :: hx(:,:)
      real(8),           intent(inout) :: hx_out(:,:)

      integer :: i_data_read,i_data_read_first,i_data_read_last,i_data_write
      integer :: i_last,istation,i_station_write,nens,i_write_first
      integer :: loc,loc_last
      integer :: column_index, pass_offset

      if (obsdat%numHeader.eq.0) then
         write(*,*) 'odd input for routine obs_append'
         write(*,*) 'no stations need to be added to the obsdat.'
         return
      endif

      nens=size(hx,1)

      ! Locate the first available locations in the output
      if (obs_out%numHeader.ge.1) then
                                        ! Memorize pass_offset, since numHeader
                                        ! will change
         pass_offset=obs_headElem_i(obs_out,OBS_PAS,obs_out%numHeader) 

         i_last=1
         loc_last=obs_headElem_i(obs_out, OBS_RLN, 1)
         storedlast: do istation=2,obs_out%numHeader
            loc=obs_headElem_i(obs_out, OBS_RLN, istation)
            if (loc.gt.loc_last) then
               i_last=istation
               loc_last=loc
            endif
         enddo storedlast
         ! The first available locations in the output:
         i_station_write=obs_out%numHeader+1
         i_data_write=obs_headElem_i(obs_out, OBS_RLN, i_last) &
                     +obs_headElem_i(obs_out, OBS_NLV, i_last)
      else
         pass_offset=0
         i_station_write=1
         i_data_write=1
      endif

      stations: do istation=1,obsdat%numHeader
         i_data_read_first=obs_headElem_i(obsdat, OBS_RLN, istation)
         i_data_read_last=i_data_read_first &
                         +obs_headElem_i(obsdat, OBS_NLV, istation) -1
         i_write_first=i_data_write

         observations: do i_data_read=i_data_read_first,i_data_read_last
            do column_index=NBDY_INT_BEG,NBDY_INT_END
               if(obsdat%intBodies%odc_flavour%columnActive(column_index)) &
                  call obs_bodySet_i(obs_out, column_index, i_data_write, &
                       obs_bodyElem_i(obsdat, column_index, i_data_read))
            enddo
            do column_index=NBDY_REAL_BEG,NBDY_REAL_END
               if(obsdat%realBodies%odc_flavour%columnActive(column_index)) &
                  call obs_bodySet_r(obs_out, column_index, i_data_write, &
                       obs_bodyElem_r(obsdat, column_index, i_data_read))
            enddo
            hx_out(1:nens,i_data_write)=hx(:,i_data_read)
                                        ! Make HIND point to new header row_index
            call obs_bodySet_i(obs_out, OBS_HIND, i_data_write, i_station_write)
            i_data_write=i_data_write+1
         enddo observations

         do column_index=NHDR_INT_BEG,NHDR_INT_END
            if(obsdat%intHeaders%odc_flavour%columnActive(column_index)) &
               call obs_headSet_i(obs_out, column_index, i_station_write, &
                    obs_headElem_i(obsdat, column_index, istation))
         enddo
         call obs_headSet_i(obs_out, OBS_ONM, i_station_write, i_station_write)
         call obs_headSet_i(obs_out, OBS_RLN, i_station_write, i_write_first)

         if (obs_out%numHeader.gt.0) then
            call obs_headSet_i(obs_out, OBS_PAS, i_station_write, &
                               obs_headElem_i(obs_out,OBS_PAS,i_station_write) &
                               +pass_offset&
                              )
         end if

         do column_index=NHDR_REAL_BEG,NHDR_REAL_END
            if(obsdat%realHeaders%odc_flavour%columnActive(column_index)) &
               call obs_headSet_r(obs_out, column_index, i_station_write, &
                    obs_headElem_r(obsdat, column_index, istation))
         enddo
         obs_out%cstnid(  i_station_write)=obsdat%cstnid ( istation)
         obs_out%cfamily( i_station_write)=obsdat%cfamily( istation)

         i_station_write=i_station_write+1
      enddo stations
   end subroutine obs_append



   SUBROUTINE obs_bdy(obsdat,PVALUES,KLIST,KFLAGS,LDFLAG,PROFIL,LDERR,LDSAT, &,21
                      LDGO,LDAIRS,LDIASI,LDCRIS,n_elements_in_block, &
                      n_levels_in_block,KNT,KNDAT,KVCORD,PVCORD, &
                      KINDEX,KIDTYP,PPMIS,nvcordtyp,vcordsf, &
                      vconv,nonelev)
      use EarthConstants_mod
      use MathPhysConstants_mod
      IMPLICIT NONE

      type(struct_obs), intent(inout) :: obsdat
      INTEGER, intent(out) :: KNDAT
      INTEGER, intent(in)  :: n_elements_in_block,n_levels_in_block,KNT
      INTEGER, intent(in)  :: KVCORD,KINDEX,KIDTYP
      INTEGER, intent(in)  :: KLIST(n_elements_in_block)
      INTEGER, intent(in)  :: KFLAGS(n_elements_in_block,n_levels_in_block,KNT)
      integer, intent(in)  :: nvcordtyp,nonelev

      REAL(kind=8),intent(in)::PVALUES(n_elements_in_block,n_levels_in_block,KNT)
      REAL(kind=8),intent(in)::PVCORD(n_levels_in_block)
      REAL(kind=8),intent(in)::PROFIL(n_levels_in_block)
      REAL(kind=8),intent(in)::PPMIS
      real(kind=8),intent(in)::vconv
                                        ! vertical coordinate parameters
                                        ! for surface data
      real(kind=8),intent(in)::vcordsf(:,:)

      LOGICAL, intent(in) :: LDFLAG,LDERR,LDSAT,LDGO,LDAIRS,LDIASI,LDCRIS

      !***********************************************************************
      !
      !***s/r OBS_BDY -FILL BODY OF OBSDAT REPORT
      !
      !Author    . P. KOCLAS(CMC TEL. 4665)
      !
      !Revision:
      !          . P. Koclas *CMC/AES Sept  1994: Add call to cvt3d
      !          .   before insertion of U and V for consistency
      !          . P. Koclas *CMC/AES February  1995:
      !          .  New call sequence neccessary to :
      !          . -allow insertion of "grouped data" records in BURP files.
      !          . -allow data observed in various vertical coordinates
      !          . -observation errors no longer initialized
      !
      !          . P. Koclas *CMC/AES March     1995:
      !            -Additions for humsat and satem data
      !          .
      !          . C. Charette *ARMA Jan        2001
      !            -Max value for T-Td surface element(12203)
      !
      !           JM Belanger CMDA/SMC  Feb 2001
      !                   . 32 bits conversion
      !          . P. Koclas *CMC/CMDA Sept     2001:
      !            -set first-guess and observation errors to missing values
      !
      !          .N Wagneur CMDA/SMC  Jine 2002
      !                   . -Additions for goes data
      !          . P. Koclas *CMC/CMDA Dec      2003:
      !                -conversion for surface wind
      !          . C. Charette *ARMA/SMC Apr      2005:
      !                -Set flag bit #12(Element assimilated by analysis) to zero
      !                 (see banco-burp documentation for more detail)
      !          . A. Beaulne *CMDA/SMC  Aug 2006
      !                     -Additions for AIRS data
      !          . S. Heilliette
      !                     -Additions for IASI data
      !                     -Additions for CrIS data
      !
      !    PURPOSE : TRANSFER DATA BLOCKS EXTRACTED FROM CMC BURP FILES TO
      !              THE IN-CORE FORMAT (OBSDAT) OF THE 3-D VARIATIONAL ANALYSIS
      !
      !    ARGUMENTS:
      !     INPUT:
      !
      !           -PVALUES : DATA BLOCK
      !           -KLIST   : LIST OF BUFR ELEMENTS
      !           -KFLAGS  : QUALITY CONTROL FLAGS
      !
      !           -LDFLAG  :  .TRUE. --> INSERT FLAGS IN OBSDAT
      !                      .FALSE. --> INSERT DUMMY VALUE(2**12)
      !           -LERR    :  .TRUE. --> INSERT OBS ERROR IN OBSDAT (HUMSAT DATA)
      !           -LDSAT   :  .TRUE. --> INSERT REF PRESSURE IN OBSDAT (SATEMS)
      !           -LDGO    :  .TRUE. --> INSERT EMISSIVITIES IN OBSDAT (GOES RADIANCES)
      !           -LDAIRS  :  .TRUE. --> INSERT EMISSIVITIES IN OBSDAT (AIRS RADIANCES)
      !           -LDIASI  :  .TRUE. --> INSERT EMISSIVITIES IN OBSDAT (IASI RADIANCES)
      !
      !           -n_elements_in_block  : NUMBER OF ELEMENTS IN DATA BLOCK
      !           -n_levels_in_block    : NUMBER OF LEVELS IN DATA BLOCK
      !           -KNT     :  THIRD DIMENSION OF DATA BLOCK
      !           -KNDAT   :  THIRD DIMENSION OF DATA BLOCK
      !           -KVCORD  :  BUFR ELEMENT CODE OF VERTICAL COORDINATE
      !           -PVCORD  :  VERTICAL COORDINATE VALUES EXTRACTED FROM DATA BLOCK
      !           -KINDEX  :  THIRD DIMENSION INDEX OF DATA BLOCK
      !           -PPMIS   :  VALUE OF MISSING DATA
      !           -VCONV   :  CONVERSION FACTOR FOR PRESSURE CO-ORDINATE
      !
      !    OUTPUT:
      !           -KNDAT   : NUMBER OF DATA INSERTED IN OBSDAT FILE
      !
      !***********************************************************************

      INTEGER ILEM,IND,IIND,IP,IK
      INTEGER IBAD,IFLAG
      INTEGER ielement,ilevel
      INTEGER ZESMAX,ZES

      REAL(kind=8) ZFACT,padd,pmul,ZEMFACT,pvalue

      !***********************************************************************
      !     SET BAD FLAG VALUE IIND AND UNIT CONVERSION CONSTANTS
      !***********************************************************************

      IIND  =-1
      IBAD=2**11

      ZFACT=VCONV

      ZEMFACT=0.01
      ZESMAX=30.

      IP=obsdat%numBody + 1
      IND=0

      !***********************************************************************
      !     PUT ALL NON MISSING DATA IN OBSDAT FILE
      !     EXIT IF THERE IS MORE DATA AVAILABLE THAN ALLOCATED TO OBSDAT FILE
      !     DATA IS CONVERTED TO UNITS USED BY 3D-VAR ANALYSIS.
      !***********************************************************************

      IK= KINDEX
      DO ielement=1,n_elements_in_block
         ILEM=obs_get_obs_index_for_bufr_element(KLIST(ielement))
         IF ( (ILEM .GT. 0) .AND. (KLIST(ielement) .NE. KVCORD) ) THEN
            DO ilevel=1,n_levels_in_block
               if(pvcord(ilevel) .ne. ppmis .and. (nonelev .eq. -1 .or. &
                  nonelev .eq. nint(pvcord(ilevel)*zfact))) then
                  IF  ( PVALUES (ielement,ilevel,IK) .NE. PPMIS ) THEN
                     pvalue=PVALUES(ielement,ilevel,IK)
                     IF ( IP + IND .LE. obsdat%numBody_max ) THEN
                                        ! VERTICAL COORDINATE
                        call obs_bodySet_r(obsdat, OBS_PPP, IP+IND, &
                               real(PVCORD(ilevel) *ZFACT +vcordsf(ilem,kidtyp),&
                               OBS_REAL))

                                        !  FOR PNM HEIGHT IS SET TO 0
                                        ! ----------------------------
                        IF ( ILEM .EQ. 53 ) THEN
                           call obs_bodySet_r(obsdat, OBS_PPP, IP+IND, &
                                              real(0.D0,OBS_REAL))
                        ENDIF
                                        ! ----------------------------

                                        ! IF ( ILEM .EQ. 2 ) Units:  V
                                        ! CONVERT TO GZ
                        IF ( ILEM .EQ. 3 ) THEN
                           pvalue=RG*pvalue
                        ENDIF
                                        ! IF ( ILEM .EQ. 4 ) Units: METERS
                                        ! IF ( ILEM .EQ. 8 ) Units:  CELSIUS
                                        ! Max value T-Td upper air
                        IF ( ILEM .EQ. 9 ) THEN
                           IF ( pvalue .GT. ZESMAX) THEN
                              pvalue=ZESMAX
                           ENDIF
                        ENDIF
                                        ! Max value T-Td surface
                        IF ( ILEM .EQ. 11 ) THEN
                           IF ( pvalue .GT. ZESMAX) THEN
                              pvalue=ZESMAX
                           ENDIF
                        ENDIF
                                        ! CONVERT TO RADIANS
                        IF ( ILEM .EQ. 48 .OR. ILEM .EQ. 54 ) THEN
                           pvalue=MPC_RADIANS_PER_DEGREE_R8*pvalue
                        ENDIF
                                        ! FLAGS
                        IF  (LDFLAG) THEN
                                        ! SET BIT 12  TO ZERO
                                        ! (Element assim by 3dvar)
                           IFLAG = KFLAGS(ielement,ilevel,IK)
                           IFLAG = IBCLR(IFLAG,12)
                           call obs_bodySet_i(obsdat, OBS_FLG, IP+IND, IFLAG)
                        ELSE
                           call obs_bodySet_i(obsdat, OBS_FLG, IP+IND, IBAD)
                        ENDIF

                        call obs_bodySet_r(obsdat, OBS_VAR, IP+IND, real(pvalue,OBS_REAL))
                        call obs_bodySet_i(obsdat, OBS_VNM, IP+IND, KLIST(ielement))
                        call obs_bodySet_i(obsdat, OBS_VCO, IP+IND, NVCORDTYP)
                        call obs_bodySet_r(obsdat, OBS_OMP, IP+IND, real(PPMIS,OBS_REAL))
                        call obs_bodySet_r(obsdat, OBS_OMA, IP+IND, real(PPMIS,OBS_REAL))
                        call obs_bodySet_r(obsdat, OBS_HPHT, IP+IND, real(PPMIS,OBS_REAL))
                        call obs_bodySet_r(obsdat, OBS_OER, IP+IND, real(PPMIS,OBS_REAL))
                        !
                        ! OBS ERROR FOR HUMSAT
                        !
                        IF ( LDERR ) THEN
                           call obs_bodySet_r(obsdat, OBS_OER, IP+IND, real(PROFIL(ilevel),OBS_REAL))
                        ENDIF
                        !
                        ! REFERENCE LEVEL FOR SATEMS
                        !
                        IF ( LDSAT ) THEN
                           call obs_bodySet_r(obsdat, OBS_OER, IP+IND, &
                                             real(PROFIL(ilevel)*ZFACT,OBS_REAL))
                           call obs_bodySet_r(obsdat, OBS_OER, IP+IND, &
                                              real(1.0D0,OBS_REAL))
                        ENDIF
                        !
                        ! SURFACE EMISSIVITIES FOR GOES AIRS AND IASI RADIANCES
                        !
                        IF ( LDGO ) THEN
                           call obs_bodySet_r(obsdat, OBS_SEM, IP+IND, &
                                           real(PROFIL(ilevel)*ZEMFACT,OBS_REAL))
                        ENDIF

                        IF ( LDAIRS ) THEN
                           call obs_bodySet_r(obsdat, OBS_SEM, IP+IND, &
                                           real(PROFIL(ilevel)*ZEMFACT,OBS_REAL))
                        END IF

                        IF ( LDIASI ) THEN
                           call obs_bodySet_r(obsdat, OBS_SEM, IP+IND, &
                                           real(PROFIL(ilevel)*ZEMFACT,OBS_REAL))
                        END IF

                        IF ( LDCRIS ) THEN
                           call obs_bodySet_r(obsdat, OBS_SEM, IP+IND, &
                                real(PROFIL(ilevel)*ZEMFACT,OBS_REAL))
                        END IF

                        IND=IND + 1
                     ELSE
                        !==================================================
                        KNDAT = IND
                        obsdat%numBody = obsdat%numBody + KNDAT
                        !==================================================
                        RETURN
                     ENDIF
                  ENDIF
               ENDIF
            END DO
         ENDIF
      END DO
      !=============================
      KNDAT = IND
      !=============================

      RETURN
   END SUBROUTINE obs_bdy



   function obs_bodyElem_i(obsdat,column_index,row_index) result(value_i) 341,1
      !func obs_bodyElem_i - Get an integer-valued body observation-data element
      !
      ! PURPOSE:
      !      To control access to the observation object.  Returns the (integer)
      !      value of the row_index'th ObsData element with the indicated column
      !      index from the "body".
      !
      ! author  : M. Buehner - 2012
      !
      implicit none
      integer                       :: value_i
      type(struct_obs), intent(in)  :: obsdat
      integer         , intent(in)  :: column_index
      integer         , intent(in)  :: row_index

      real(OBS_REAL) :: value_r         ! not used

      call odc_columnElem(obsdat%intBodies, column_index, row_index, &
                          value_i, value_r)
   end function obs_bodyElem_i



   function obs_bodyElem_r(obsdat,column_index,row_index) result(value_r) 256,1
      !func obs_bodyElem_r - Get a real-valued body observation-data element
      !
      ! PURPOSE:
      !      To control access to the observation object.  Returns the (real)
      !      value of the row_index'th ObsData element with the indicated column
      !      index from the "body".
      !
      ! author  : M. Buehner - 2012
      !
      implicit none
      real(OBS_REAL)                :: value_r
      type(struct_obs), intent(in)  :: obsdat
      integer         , intent(in)  :: column_index
      integer         , intent(in)  :: row_index

      integer :: value_i                ! not used

      call odc_columnElem(obsdat%realBodies, column_index, row_index, &
                          value_i, value_r)
   end function obs_bodyElem_r



   function obs_bodyIndex_mpiglobal(obsdat,row_index) result(value)
      !func obs_bodyIndex_mpiglobal - Get the mpiglobal body row_index
      !
      ! PURPOSE:
      !      To control access to the mpiglobal row_index into the "body".
      !
      ! author  : M. Buehner
      !
      implicit none
      integer value
      type(struct_obs), intent(in)  :: obsdat
      integer         , intent(in)  :: row_index

      value=obsdat%bodyIndex_mpiglobal(row_index)
   end function obs_bodyIndex_mpiglobal



   subroutine obs_bodySet_i(obsdat, column_index, row_index, value_i) 115,1
      !s/r obs_bodySet_i - set an integer-valued body observation-data element
      !
      ! PURPOSE:
      !      To control access to the observation object.  Sets the (integer)
      !      value of the row_index'th ObsData element with the indicated column
      !      index from the "body".
      !
      ! author  : J.W. Blezius and M. Buehner - 2012
      !
      implicit none
      type(struct_obs), intent(inout)  :: obsdat
      integer         , intent(in)     :: column_index
      integer         , intent(in)     :: row_index
      integer         , intent(in)     :: value_i

      call odc_columnSet(obsdat%intBodies, column_index, row_index, &
                         value_i, NULL_COLUMN_VALUE_R, &
                         obsdat%numBody, obsdat%numBody_max)
   end subroutine obs_bodySet_i



   subroutine obs_bodySet_r(obsdat, column_index, row_index, value_r) 147,1
      !s/r obs_bodySet_r - set a real-valued body observation-data element
      !
      ! PURPOSE:
      !      To control access to the observation object.  Sets the (real)
      !      value of the row_index'th ObsData element with the indicated column
      !      index from the "body".
      !
      ! author  : J.W. Blezius and M. Buehner - 2012
      !
      implicit none
      type(struct_obs), intent(inout)  :: obsdat
      integer         , intent(in)     :: column_index
      integer         , intent(in)     :: row_index
      real(OBS_REAL)  , intent(in)     :: value_r

      call odc_columnSet(obsdat%realBodies, column_index, row_index, &
                         NULL_COLUMN_VALUE_I, value_r, &
                         obsdat%numBody, obsdat%numBody_max)
   end subroutine obs_bodySet_r



   subroutine obs_class_initialize(obsColumnMode_in, myip) 1,2
      !s/r obs_class_initialize - Set observation-data class variables.
      !
      ! PURPOSE:
      !      Set variables that take the same value for all instances of the
      !      class.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      ! mode controlling the subset of columns that are activated in all objects
      character(len=*), intent(in), optional :: obsColumnMode_in
      integer, intent(in), optional :: myip

      integer :: myip_

      ! The 'save' makes this a CLASS-CONSTANT variable
      character(len=12), save :: obsColumnMode_class = '            '

      integer :: column_index

      INITIALIZED: if(.not. obs_class_initialized) then
         obs_class_initialized = .true.

         if(present(myip)) then
            myip_ = myip
         else
            ! Make an assumption:  cause every processor to write much to stdout
            myip_ = 0
         end if

         ! Determine which columns will be initially active
         if(present(obsColumnMode_in)) then
            obsColumnMode_class=trim(obsColumnMode_in)
         else
            obsColumnMode_class='ALL '
         endif

         write(*,*)'OBS_CLASS_INITIALIZE: obsColumnMode=', &
                   trim(obsColumnMode_class)

         ! DELEGATE THE REAL WORK TO THE ODC CLASS
         ! DELEGATE THE REAL WORK TO THE ODC CLASS
         call odc_class_initialize(obsColumnMode_class, myip_)

      else INITIALIZED
         write(*,*) 'obs_class_initialize: !!! WARNING WARNING WARNING!!!'
         write(*,*) 'obs_class_initialize: already called before, not ' &
                    // 're-activating columns'
         write(*,*) 'obs_class_initialize: !!! WARNING WARNING WARNING!!!'
         if(present(obsColumnMode_in)) then
            if(trim(obsColumnMode_in).ne.trim(obsColumnMode_class)) then
               call obs_abort('obs_class_initialize: called with different '&
                            //'value of obsColumnMode than during first call: '&
                            // trim(obsColumnMode_class) // ' .ne. ' &
                            // trim(obsColumnMode_in))
               return
            endif
         endif
      endif INITIALIZED
   end subroutine obs_class_initialize



   subroutine obs_clean(obsdat,hx,nens,nobsout,qcvar),25
      !
      ! object  - remove all observations from the obsdat  
      !         that will not be assimilated. 
      !
      !author  : Peter Houtekamer
      !     revision may 2005. Houtekamer and Mitchell. Addition of the
      !          hx and nens arguments
      !
      !arguments
      !     nobsout       : unit number for the ASCII output
      !     qcvar         : input logical indicating if the input obsdat 
      !                     data have benefited from a qc-var procedure
      !
      !the logic applied:
      !     A body (and its associated header)
      !     will be retained if these three conditions are all met:
      !        1) either of:
      !             1a) btest(obsdat%intBodies%columns(OBS_FLG,jdata),12)
      !             1b) .not. qcvar (the 5th parameter of obs_clean)
      !        2) obsdat% intBodies%columns(OBS_ASS,jdata) .eq. 1
      !        3) obsdat%realBodies%columns(OBS_ZHA,jdata) .ge. 0.0
      !
      implicit none

      type (struct_obs), intent(inout) :: obsdat

      real(8), intent(inout) :: hx(:,:)
      integer, intent(in)    :: nens, nobsout
      logical, intent(in)    :: qcvar

      integer :: iaccept,idata,ipnt,iwrite
      integer :: jdata,kobs,var3d,kobsout
      integer :: column_index
      integer :: active_index

      write(nobsout,'(1x,A,I7)') 'stations prior to cleanup: ', obsdat%numHeader
      write(*,*) 'enter obs_clean'

      kobsout=0 
      iwrite=0
      stations: do kobs=1,obsdat%numHeader
         ipnt  = obs_headElem_i(obsdat, OBS_RLN, kobs)
         idata = obs_headElem_i(obsdat, OBS_NLV, kobs)
         iaccept=0
         observations: do jdata = ipnt, ipnt + idata - 1
            if (     btest(obs_bodyElem_i(obsdat, OBS_FLG, jdata),12) &
                .or. .not. qcvar) then 
               ! data will be accepted if they went through the variational
               ! system  including the qcvar. They will also be accepted if the
               ! qcvar procedure was not applied (i.e. when backalt files are
               ! used as input).
               var3d=1
            else
               var3d=0
            endif

            ! To remove observations for which the height in the atmosphere has
            ! not been assigned (for instance because they are above the model
            ! top for the EnKF system)
            if (obs_bodyElem_r(obsdat, OBS_ZHA, jdata) .lt. 0.) then
               call obs_bodySet_i(obsdat, OBS_ASS, jdata, -1)
            endif

            if (      (obs_bodyElem_i(obsdat, OBS_ASS, jdata).eq.1) &
                 .and.(var3d.eq.1)) then 
               ! the observation will be used in the analysis
               iaccept=iaccept+1
               iwrite=iwrite+1
               do active_index=1,odc_numActiveColumn(obsdat%intBodies)
                  column_index=odc_columnIndexFromActiveIndex( &
                                       obsdat%intBodies%odc_flavour,active_index)
                     call obs_bodySet_i (obsdat, column_index, iwrite, &
                          obs_bodyElem_i(obsdat, column_index, jdata))
               enddo
               do active_index=1,odc_numActiveColumn(obsdat%realBodies)
                  column_index=odc_columnIndexFromActiveIndex( &
                                      obsdat%realBodies%odc_flavour,active_index)
                     call obs_bodySet_r (obsdat, column_index, iwrite, &
                          obs_bodyElem_r(obsdat, column_index, jdata))
               enddo
               hx(1:nens,iwrite)=hx(1:nens,jdata)
               ! Revise the header row cross-index to match the revised headers
               call obs_bodySet_i (obsdat, OBS_HIND, iwrite, kobsout+1)
            endif
         enddo observations

         ! adjust obsdat%realHeaders%columns 
         if (iaccept.gt.0) then
            kobsout=kobsout+1
            do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
               column_index = odc_columnIndexFromActiveIndex( &
                                      obsdat%intHeaders%odc_flavour,active_index)
               call obs_headSet_i (obsdat, column_index, kobsout, &
                    obs_headElem_i(obsdat, column_index, kobs))
            enddo
            do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
               column_index = odc_columnIndexFromActiveIndex( &
                                     obsdat%realHeaders%odc_flavour,active_index)
               call obs_headSet_r (obsdat, column_index, kobsout, &
                    obs_headElem_r(obsdat, column_index, kobs))
            enddo
            obsdat%cstnid(kobsout)=obsdat%cstnid(kobs)
            obsdat%cfamily(kobsout)=obsdat%cfamily(kobs)
            ! Revise the body cross-indices to match the revised bodies
            call obs_headSet_i(obsdat, OBS_NLV, kobsout, iaccept)
            call obs_headSet_i(obsdat, OBS_RLN, kobsout, iwrite-iaccept+1)
         endif
      enddo stations
      obsdat%numHeader=kobsout
      obsdat%numBody = iwrite

      write(nobsout,*) 'after cleanup of the cma: '
      write(nobsout,'(1x,A,I7)') &
         'number of stations containing valid data   ',obsdat%numHeader
      write(nobsout,'(1x,A,I7)') & 
         'number of observations now in the cma file ',obsdat%numBody

   end subroutine obs_clean



   function obs_columnActive_IB(obsdat,column_index) result(columnActive)
      !
      ! PURPOSE:
      !      Return the active status for a column
      !
      ! author  : M. Buehner - 2013 
      !
      implicit none
      type (struct_obs), intent(inout) :: obsdat
      integer :: column_index
      logical :: columnActive

      columnActive = obsdat%intBodies%odc_flavour%columnActive(column_index)

   end function obs_columnActive_IB



   function obs_columnActive_IH(obsdat,column_index) result(columnActive) 1
      !
      ! PURPOSE:
      !      Return the active status for a column
      !
      ! author  : M. Buehner - 2013 
      !
      implicit none
      type (struct_obs), intent(inout) :: obsdat
      integer :: column_index
      logical :: columnActive

      columnActive = obsdat%intHeaders%odc_flavour%columnActive(column_index)

   end function obs_columnActive_IH



   function obs_columnActive_RB(obsdat,column_index) result(columnActive)
      !
      ! PURPOSE:
      !      Return the active status for a column
      !
      ! author  : M. Buehner - 2013 
      !
      implicit none
      type (struct_obs), intent(inout) :: obsdat
      integer :: column_index
      logical :: columnActive

      columnActive = obsdat%realBodies%odc_flavour%columnActive(column_index)

   end function obs_columnActive_RB



   function obs_columnActive_RH(obsdat,column_index) result(columnActive) 2
      !
      ! PURPOSE:
      !      Return the active status for a column
      !
      ! author  : M. Buehner - 2013 
      !
      implicit none
      type (struct_obs), intent(inout) :: obsdat
      integer :: column_index
      logical :: columnActive

      columnActive = obsdat%realHeaders%odc_flavour%columnActive(column_index)

   end function obs_columnActive_RH



   function obs_columnIndexFromName(odc_flavour, column_name) & 4,1
                                                         result(column_index_out)
      !
      ! PURPOSE:
      !      Situations do occur where the client knows only the name of a
      !      column, but needs to know its index. This method supplies the index.
      !
      ! author  : J.W. Blezius - 2013 - extracted from M Buehner's
      !                                 obs_columnIndexFromName_IB
      !
      implicit none
      type(struct_odc_flavour), intent(in) :: odc_flavour
      character(len=*)        , intent(in) :: column_name
      integer                              :: column_index_out

      integer            :: column_index
      logical            :: lfound
      character(len=100) :: message

      lfound=.false.
      do column_index=odc_flavour%ncol_beg, odc_flavour%ncol_end
         if(  trim(column_name) &
            ==trim(odc_flavour%columnNameList(column_index)))then
            lfound=.true.
            column_index_out = column_index
            exit
         endif
      enddo

      if(.not.lfound) then
         write(message,*)'abort in obs_columnIndexFromName (' &
                       // odc_flavour%dataType //','// odc_flavour%headOrBody //&
                       '): name not found='// column_name
         call obs_abort(message); return
      end if
   end function obs_columnIndexFromName



   function obs_columnIndexFromName_IB(column_name) result(column_index),1
      !
      ! PURPOSE:
      !      This wrapper around obs_columnIndexFromName selects the data-column
      !      flavour.
      !
      ! author  : Mark Buehner - 2012
      !           J.W. Blezius - 2013 - contents extracted to
      !                                 obs_columnIndexFromName
      !
      implicit none
      character(len=*), intent(in) :: column_name
      integer                      :: column_index

      column_index = obs_columnIndexFromName(odc_flavour_IB, column_name)
   end function obs_columnIndexFromName_IB



   function obs_columnIndexFromName_IH(column_name) result(column_index),1
      !
      ! PURPOSE:
      !      This wrapper around obs_columnIndexFromName selects the data-column
      !      flavour.
      !
      ! author  : Mark Buehner - 2012
      !           J.W. Blezius - 2013 - contents extracted to
      !                                 obs_columnIndexFromName
      !
      implicit none
      character(len=*), intent(in) :: column_name
      integer                      :: column_index

      column_index = obs_columnIndexFromName(odc_flavour_IH, column_name)
   end function obs_columnIndexFromName_IH



   function obs_columnIndexFromName_RB(column_name) result(column_index),1
      !
      ! PURPOSE:
      !      This wrapper around obs_columnIndexFromName selects the data-column
      !      flavour.
      !
      ! author  : Mark Buehner - 2012
      !           J.W. Blezius - 2013 - contents extracted to
      !                                 obs_columnIndexFromName
      !
      implicit none
      character(len=*), intent(in) :: column_name
      integer                      :: column_index

      column_index = obs_columnIndexFromName(odc_flavour_RB, column_name)
   end function obs_columnIndexFromName_RB



   function obs_columnIndexFromName_RH(column_name) result(column_index),1
      !
      ! PURPOSE:
      !      This wrapper around obs_columnIndexFromName selects the data-column
      !      flavour.
      !
      ! author  : Mark Buehner - 2012
      !           J.W. Blezius - 2013 - contents extracted to
      !                                 obs_columnIndexFromName
      !
      implicit none
      character(len=*), intent(in) :: column_name
      integer                      :: column_index

      column_index = obs_columnIndexFromName(odc_flavour_RH, column_name)
   end function obs_columnIndexFromName_RH



   subroutine obs_comm(obsdat,myip,nens,nstncom,hx),30
      !authors  Peter Houtekamer and Herschel Mitchell May 2005
      !     (this routine evolved from the earlier routine commstns that worked
      !      per analysis pass and did not consider hx).
      !
      !object: communicate information on the stations and the observations
      !        between the processes
      !
      !input variables:
      !     myip: number of the processor.
      !     nens: number of ensemble members for hx (may be zero)
      !     nstncom: we wish to exchange the obsdat for stations 1 ... nstncom
      !       (nstncom may be less than obsdat%numHeader_max).

      implicit none

      type (struct_obs), intent(inout) :: obsdat
      integer,           intent(in)    :: myip,nens,nstncom
      real(8),           intent(inout), dimension(:,:) :: hx    

      integer :: column_index
      integer :: active_index
      integer       , pointer :: intHeaders_tmp(:,:),intBodies_tmp(:,:)
      real(OBS_REAL), pointer :: realHeaders_tmp(:,:),realBodies_tmp(:,:)
      integer :: ier,master,mxstn,ncomm,nobs
      character(len=100) :: message
#if OBS_REAL==4
      character(len=*), parameter :: MPI_OBS_REAL="mpi_real"
#elif OBS_REAL==8
      character(len=*), parameter :: MPI_OBS_REAL="mpi_double_precision"
#endif

      ! broadcast relevant integers from master to all processes 

      master=0

      ! if nothing to communicate, return
      if (obsdat%numHeader_max.le.0) return

      if (nstncom.gt.obsdat%numHeader_max) then
         write(message,*) 'ERROR in obs_comm: nstncom ',nstncom, &
            ' may not exceed numHeader_max ',obsdat%numHeader_max
         call obs_abort(message); return
      endif
      if (nstncom.le.0) then
         call obs_abort('OBS_COMM: nstncom should be positive'); return
      endif

      ! Nonmaster processes need to know how many body elements they will receive
      nobs=  obs_headElem_i(obsdat, OBS_RLN, nstncom) &
           + obs_headElem_i(obsdat, OBS_NLV, nstncom) - 1
      ncomm=1
      call rpn_comm_bcast(nobs,ncomm,"mpi_integer",master,"world",ier)

      if (nobs.gt.obsdat%numBody_max) then
         write(message,*) 'ERROR in obs_comm: nobs ',nobs, &
            ' may not exceed obsdat%numBody_max ',obsdat%numBody_max
         call obs_abort(message); return
      endif
      if (nobs.le.0) then
         write(message,*) 'ERROR in obs_comm: nobs ',nobs,' should be positive. '
         call obs_abort(message); return
      endif

      ! extract data from active columns before broadcasting them
      ncomm=nstncom*odc_numActiveColumn(obsdat%intHeaders)
      allocate(intHeaders_tmp(odc_numActiveColumn(obsdat%intHeaders),nobs))
      do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
         column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%intHeaders%odc_flavour, active_index)
         intHeaders_tmp(active_index,1:nstncom) &
                      =obsdat%intHeaders%columns(column_index)%value_i(1:nstncom)
      enddo
      call rpn_comm_bcast(intHeaders_tmp,ncomm,"mpi_integer",master,"world",ier)
      ! put data from active columns back into object
      do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
         column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%intHeaders%odc_flavour, active_index)
         obsdat%intHeaders%columns(column_index)%value_i(1:nstncom) &
                                          =intHeaders_tmp(active_index,1:nstncom)
      enddo
      deallocate(intHeaders_tmp)

      ! extract data from active columns before broadcasting them
      ncomm=nstncom*odc_numActiveColumn(obsdat%realHeaders)
      allocate(realHeaders_tmp(odc_numActiveColumn(obsdat%realHeaders),nobs))
      do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
         column_index=odc_columnIndexFromActiveIndex( &
                                    obsdat%realHeaders%odc_flavour, active_index)
         realHeaders_tmp(active_index,1:nstncom) &
                     =obsdat%realHeaders%columns(column_index)%value_r(1:nstncom)
      enddo
      call rpn_comm_bcast(realHeaders_tmp,ncomm,MPI_OBS_REAL,master,"world",ier)
      ! put data from active columns back into object
      do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
         column_index=odc_columnIndexFromActiveIndex( &
                                    obsdat%realHeaders%odc_flavour, active_index)
         obsdat%realHeaders%columns(column_index)%value_r(1:nstncom) &
                                         =realHeaders_tmp(active_index,1:nstncom)
      enddo
      deallocate(realHeaders_tmp)

      ncomm=nstncom*len(obsdat%cstnid(0))
      call rpn_comm_bcastc(obsdat%cstnid,ncomm,"mpi_character",master,"world", &
                                                                             ier)
      ncomm=nstncom*len(obsdat%cfamily(0))
      call rpn_comm_bcastc(obsdat%cfamily,ncomm,"mpi_character",master,"world", &
                                                                             ier)

      ! extract data from active columns before broadcasting them
      ncomm=nobs*odc_numActiveColumn(obsdat%intBodies)
      allocate(intBodies_tmp(odc_numActiveColumn(obsdat%intBodies),nobs))
      do active_index=1,odc_numActiveColumn(obsdat%intBodies)
         column_index=odc_columnIndexFromActiveIndex( &
                                      obsdat%intBodies%odc_flavour, active_index)
         intBodies_tmp(active_index,1:nobs) &
                          =obsdat%intBodies%columns(column_index)%value_i(1:nobs)
      enddo
      call rpn_comm_bcast(intBodies_tmp,ncomm,"mpi_integer",master,"world",ier)
      ! put data from active columns back into object
      do active_index=1,odc_numActiveColumn(obsdat%intBodies)
         column_index=odc_columnIndexFromActiveIndex( &
                                      obsdat%intBodies%odc_flavour, active_index)
         obsdat%intBodies%columns(column_index)%value_i(1:nobs) &
                                              =intBodies_tmp(active_index,1:nobs)
      enddo
      deallocate(intBodies_tmp)

      ! extract data from active columns before broadcasting them
      ncomm=nobs*odc_numActiveColumn(obsdat%realBodies)
      allocate(realBodies_tmp(odc_numActiveColumn(obsdat%realBodies),nobs))
      do active_index=1,odc_numActiveColumn(obsdat%realBodies)
         column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%realBodies%odc_flavour, active_index)
         realBodies_tmp(active_index,1:nobs) &
                         =obsdat%realBodies%columns(column_index)%value_r(1:nobs)
      enddo
      call rpn_comm_bcast(realBodies_tmp,ncomm,MPI_OBS_REAL,master,"world",ier)
      ! put data from active columns back into object
      do active_index=1,odc_numActiveColumn(obsdat%realBodies)
         column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%realBodies%odc_flavour, active_index)
         obsdat%realBodies%columns(column_index)%value_r(1:nobs) &
                                             =realBodies_tmp(active_index,1:nobs)
      enddo
      deallocate(realBodies_tmp)

      ! Broadcast the remaining obsdat variables
      ncomm=1
      call rpn_comm_bcast(obsdat%numHeader,ncomm,"mpi_integer",master,"world", &
                                                                             ier)
      call rpn_comm_bcast(obsdat%numBody,  ncomm,"mpi_integer",master,"world", &
                                                                             ier)
      call rpn_comm_bcast(obsdat%mpi_local,ncomm,"mpi_integer",master,"world", &
                                                                             ier)

      if (nens.gt.0) then
         ncomm=nobs*nens
         call rpn_comm_bcast(hx,ncomm,"mpi_double_precision",master,"world",ier)
      endif

      return

   end subroutine obs_comm



   subroutine obs_copy(obs_a,obs_b) 1
      !
      ! object  - copy an obsdat object
      !
      !author  : Peter Houtekamer. August 2011.
      !
      !arguments
      !     input : obs_a input object
      !     output: obs_b a copy of obs_a
      !
      !note:  this method assumes that obs_b has already been initialized
      !
      implicit none

      type(struct_obs), intent(in)  :: obs_a
      type(struct_obs), intent(inout) :: obs_b
      integer :: column_index

      ! check if object to be copied is empty and react appropriately
      if (obs_a%numHeader_max.eq.0 .or. obs_a%numBody_max.eq.0) then
         obs_b%numHeader     = obs_a%numHeader
         obs_b%numHeader_max = obs_a%numHeader_max
         obs_b%numBody       = obs_a%numBody
         obs_b%numBody_max   = obs_a%numBody_max
         return
      endif

      !** Commented out by M. Buehner to allow use in EnVar (also added copy of 
      !** headerIndex_mpiglobal and bodyIndex_mpiglobal, if they exist)
      !if(obs_a%mpi_local)then
      !   call obs_abort( &
      !         'obs_copy() is not equipped to handle the case, mpi_local=.true.')
      !   return
      !end if

      do column_index=NHDR_REAL_BEG,NHDR_REAL_END
         if(obs_a%realHeaders%odc_flavour%columnActive(column_index))  &
              obs_b%realHeaders%columns(column_index)%value_r(:) &
            = obs_a%realHeaders%columns(column_index)%value_r(:)
      enddo
      do column_index=NHDR_INT_BEG,NHDR_INT_END
         if(obs_a%intHeaders%odc_flavour%columnActive(column_index))  &
              obs_b%intHeaders%columns(column_index)%value_i(:) &
            = obs_a%intHeaders%columns(column_index)%value_i(:)
      enddo
      do column_index=NBDY_REAL_BEG,NBDY_REAL_END
         if(obs_a%realBodies%odc_flavour%columnActive(column_index))  &
              obs_b%realBodies%columns(column_index)%value_r(:) &
            = obs_a%realBodies%columns(column_index)%value_r(:)
      enddo
      do column_index=NBDY_INT_BEG,NBDY_INT_END
         if(obs_a%intBodies%odc_flavour%columnActive(column_index))  &
              obs_b%intBodies%columns(column_index)%value_i(:) &
            = obs_a%intBodies%columns(column_index)%value_i(:)
      enddo
      obs_b%cstnid(:)     = obs_a%cstnid(:)
      obs_b%cfamily(:)    = obs_a%cfamily(:)

      obs_b%numHeader     = obs_a%numHeader
      obs_b%numHeader_max = obs_a%numHeader_max
      obs_b%numBody       = obs_a%numBody
      obs_b%numBody_max   = obs_a%numBody_max

      if(associated(obs_a%headerIndex_mpiglobal)) then
        write(*,*) 'obs_copy: copying headerIndex_mpiglobal'
        if(associated(obs_b%headerIndex_mpiglobal)) then
          deallocate(obs_b%headerIndex_mpiglobal)
        endif
        allocate(obs_b%headerIndex_mpiglobal(size(obs_a%headerIndex_mpiglobal)))
        obs_b%headerIndex_mpiglobal(:) = obs_a%headerIndex_mpiglobal(:)
      endif

      if(associated(obs_a%bodyIndex_mpiglobal)) then
        write(*,*) 'obs_copy: copying bodyIndex_mpiglobal'
        if(associated(obs_b%bodyIndex_mpiglobal)) then
          deallocate(obs_b%bodyIndex_mpiglobal)
        endif
        allocate(obs_b%bodyIndex_mpiglobal(size(obs_a%bodyIndex_mpiglobal)))
        obs_b%bodyIndex_mpiglobal(:) = obs_a%bodyIndex_mpiglobal(:)
      endif

      obs_b%mpi_local     = obs_a%mpi_local
   end subroutine obs_copy



   subroutine obs_count_headers(obsdat,kulout),3
      !
      ! object - count the number of stations and
      !         observations that are in the obsdat.
      !
      !author  : Peter Houtekamer
      !
      !arguments
      !     input:    kulout: unit number for ASCII error messages and 
      !                  observation counts.
      !
      implicit none

      integer, parameter :: MAXID = 256
      type (struct_obs), intent(in) :: obsdat
      integer, intent(in) :: kulout

      integer  :: allstn,allobs,id,idata,kobs
      integer, dimension(MAXID)   :: numobs,numstn
      character(len=100) :: message

      ! initialize totals to zero
      numstn(:)=0
      numobs(:)=0
      allstn=0
      allobs=0

      do kobs=1,obsdat%numHeader
         id=obs_headElem_i(obsdat, OBS_ITY, kobs)
         if(id.gt.MAXID) then
            id=mod(id,1000)
         endif
         if ((id.lt.1).or.(id.gt.MAXID)) then 
            write(message,*)'OBS_COUNT_HEADERS: ITY (instrument and ' // &
                            'retrieval type) out of range: ', id
            write(kulout,*)message
            call obs_abort(message); return
         endif
         numstn(id)=numstn(id)+1
         ! idata: number of obs for this station
         idata = obs_headElem_i(obsdat, OBS_NLV, kobs)
         numobs(id)=numobs(id)+idata
      enddo

      write(kulout,*) 'number of stations and observations'
      write(kulout,*) ' idtype #stations #observations '
      do id=1,MAXID
         if (numstn(id).gt.0) then
            write(kulout,'(i3,3x,i7,2x,i8)') id,numstn(id),numobs(id)
         endif
         allstn=allstn+numstn(id)
         allobs=allobs+numobs(id)
      enddo
      write(kulout,'(1x,A,I7)') 'total number of stations:     ',allstn
      write(kulout,'(1x,A,I7)') 'total number of observations: ',allobs

      return
   end subroutine obs_count_headers



   subroutine obs_deallocate(obsdat) 3,6
      !s/r obs_deallocate - De-allocate the object's arrays.
      !
      ! PURPOSE:
      !      De-allocate arrays.  This is a private method.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none

      type(struct_obs), intent(inout) :: obsdat

      integer :: ierr
      integer :: column_index

      if(.not.obsdat%allocated) then
         ! The object content has already been de-allocated.  Don't repeat it.
         return
      endif

      obsdat%allocated=.false.
      HEADER:if(obsdat%numHeader_max.gt.0) then
         if (associated(obsdat%cfamily)) then
            deallocate(obsdat%cfamily,STAT=ierr)
            nullify(obsdat%cfamily)
            if(ierr.ne.0)write(*,*) 'Problem detected with CFAMILY. IERR =',ierr
         end if

         if (associated(obsdat%cstnid))then
            deallocate(obsdat%cstnid,STAT=ierr)
            nullify(obsdat%cstnid)
            if(ierr.ne.0)write(*,*) 'Problem detected with CSTNID. IERR =',ierr
         end if

         if (associated(obsdat%realHeaders%columns))then
            do column_index=NHDR_REAL_BEG,NHDR_REAL_END
               if(obsdat%realHeaders%odc_flavour%columnActive(column_index)) &
                  call odc_deallocate(obsdat%realHeaders%columns(column_index))
            enddo
         end if

         if (associated(obsdat%intHeaders%columns))then
            do column_index=NHDR_INT_BEG,NHDR_INT_END
               if(obsdat%intHeaders%odc_flavour%columnActive(column_index)) &
                  call odc_deallocate(obsdat%intHeaders%columns(column_index))
            enddo
         end if

         deallocate(obsdat%scratchRealHeader)
         nullify   (obsdat%scratchRealHeader)

         deallocate(obsdat%scratchIntHeader)
         nullify   (obsdat%scratchIntHeader)
      endif HEADER

      BODY:if(obsdat%numBody_max.gt.0) then
         if (associated(obsdat%realBodies%columns))then
            do column_index=NBDY_REAL_BEG,NBDY_REAL_END
               if(obsdat%realBodies%odc_flavour%columnActive(column_index)) &
                  call odc_deallocate(obsdat%realBodies%columns(column_index))
            enddo
         end if

         if (associated(obsdat%intBodies%columns))then
            do column_index=NBDY_INT_BEG,NBDY_INT_END
               if(obsdat%intBodies%odc_flavour%columnActive(column_index)) &
                  call odc_deallocate(obsdat%intBodies%columns(column_index))
            enddo
         end if

         deallocate(obsdat%scratchRealBody)
         nullify   (obsdat%scratchRealBody)

         deallocate(obsdat%scratchIntBody)
         nullify   (obsdat%scratchIntBody)
      endif BODY

      obsdat%numHeader_max=0
      obsdat%numBody_max=0

      call ild_finalize(obsdat%header_index_list_depot)
      call ild_finalize(obsdat%body_index_list_depot)

   end subroutine obs_deallocate



   function obs_elem_c(obsdat,name,row_index) result(value) 24,1
      !func obs_elem_c - Get a character-string-valued observation-data element
      !
      ! PURPOSE:
      !      To control access to the observation object.  Returns the
      !      (character) value of the ObsData element with the indicated name
      !      and row_index.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none

      character(len=12) :: value
      type(struct_obs), intent(in)  :: obsdat
      character(len=*), intent(in)  :: name
      integer         , intent(in)  :: row_index

      select case (trim(name))
      case ('STID'); value=obsdat%cstnid(row_index)

      case default
         call obs_abort('ERROR:  ' // name(1:4) // &
                        ' is not a character(len=*) observation.');return
      end select
   end function obs_elem_c



   subroutine obs_enkf_bdy(obsdat,vconv, &,3
                           pvalues,klist,kflags,profil, &
                           ldairs,kndat,kvcord,pvcord,kindex,kidtyp, &
                           nvcordtyp, vcordsf)
      !s/r obs_enkf_bdy -FILL BODY OF OBSDAT REPORT
      !
      !Author    . P. KOCLAS(CMC TEL. 4665)
      !
      !Revision:
      !          . P. Koclas *CMC/AES Sept  1994: Add call to cvt3d
      !          .   before insertion of U and V for consistency
      !          . P. Koclas *CMC/AES February  1995:
      !          .  New call sequence neccessary to :
      !          . -allow insertion of "grouped data" records in BURP files.
      !          . -allow data observed in various vertical coordinates
      !          . -observation errors no longer initialized
      !
      !          . P. Koclas *CMC/AES March     1995:
      !            -Additions for humsat and satem data
      !          .
      !          . C. Charette *ARMA Jan        2001
      !            -Max value for T-Td surface element(12203)
      !
      !           JM Belanger CMDA/SMC  Feb 2001
      !                   . 32 bits conversion
      !           P. Houtekamer July 2005. Remove the lines for HUMSAT data
      !          . A. Beaulne *CMDA/SMC  Aug 2006
      !                     -Additions for AIRS data
      !           Xingxiu Deng, August 2008. added calling readpeak, calling airszha
      !                     to define realBodies(ncmzha,:) for AIRS
      !           Xingxiu Deng, July 2009. added including column.cdk, calling readip1
      !                     to get ptop and define ncmzha for AMSU-A channel 11 and 12
      !                      if ptop is equal or higher than 2 hPa.
      !
      !    PURPOSE : TRANSFER DATA BLOCKS EXTRACTED FROM CMC BURP FILES TO
      !              THE IN-CORE FORMAT (OBSDAT) OF THE ANALYSIS
      !
      !    ARGUMENTS:
      !     INPUT:
      !
      !           -PVALUES : DATA BLOCK
      !           -KLIST   : LIST OF BUFR ELEMENTS
      !           -KFLAGS  : QUALITY CONTROL FLAGS
      !
      !           -LDAIRS  :  .TRUE. --> INSERT EMISSIVITIES IN OBSDAT (AIRS RADIANCES)
      !
      !           -KVCORD  :  BUFR ELEMENT CODE OF VERTICAL COORDINATE
      !           -PVCORD  :  VERTICAL COORDINATE VALUES EXTRACTED FROM DATA BLOCK
      !           -KINDEX  :  THIRD DIMENSION INDEX OF DATA BLOCK
      !           -KIDTYP  :  burptype
      !           -vconv   :  conversion factor for pressure coordinate
      !           -profil  :  for GOES and AIRS
      !           -nvcordtyp :
      !
      !    OUTPUT:
      !           -KNDAT   : NUMBER OF DATA INSERTED IN OBSDAT FILE
      !           -OBSDAT%REALBODIES, OBSDAT%INTBODIES: obsdat body-information (new information added)
      !
      use EarthConstants_mod, only:  GRAV
      use MathPhysConstants_mod
      implicit none

      type(struct_obs), intent(inout) :: obsdat

      logical, intent(in)  :: ldairs
      integer, intent(in)  :: kidtyp,kindex,kvcord
      integer, dimension(:),     intent(in) :: klist 
      integer, dimension(:,:,:), intent(in) :: kflags
      real(kind=4), intent(in) :: vconv
      real(kind=4), dimension(:), intent(in) :: profil,pvcord
      real(kind=4), dimension(:,:,:),  intent(in) :: pvalues
      integer, intent(out) :: kndat
      integer, intent(in) :: nvcordtyp
                                        ! vertical coordinate parameters
                                        ! for surface data
      real, dimension(:,:) :: vcordsf

      real (kind=4), parameter :: PPMIS=-999.0 ! VALUE OF MISSING DATA

      integer :: ichn,ik,ilem,ind,ip,ielement,ilevel,n_elements_in_block
      integer :: n_levels_in_block,zesmax

      real(kind=4) :: pdum,pvalue,zemfact,ztorad

      ! -n_elements_in_block    : NUMBER OF ELEMENTS IN DATA BLOCK
      ! -n_levels_in_block      : NUMBER OF  LEVELS  IN DATA BLOCK
      n_elements_in_block=size(kflags,1)
      n_levels_in_block=size(kflags,2)
      !
      !     SET UNIT CONVERSION CONSTANTS
      !
      ZTORAD=MPC_RADIANS_PER_DEGREE_R4

      ZEMFACT=0.01
      ZESMAX=30.

      IP=OBSDAT%numBody + 1
      IND=0
      !
      !     PUT ALL NON MISSING DATA IN OBSDAT FILE
      !     EXIT IF THERE IS MORE DATA AVAILABLE THAN ALLOCATED TO OBSDAT FILE
      !     DATA IS CONVERTED TO UNITS USED BY 3D-VAR ANALYSIS.
      !
      IK= KINDEX
      DO ielement=1,n_elements_in_block
         ILEM=obs_get_obs_index_for_bufr_element(KLIST(ielement))
         IF ( (ILEM .GT. 0) .AND. (KLIST(ielement) .NE. KVCORD) ) THEN
            DO ilevel=1,n_levels_in_block
               if(pvcord(ilevel) .ne. ppmis) then
                  IF  ( PVALUES (ielement,ilevel,IK) .NE. PPMIS ) THEN
                     pvalue=PVALUES(ielement,ilevel,IK)
                     ! PLH replaced ndatamx by numBody_max
                     IF ( IP + IND .LE. obsdat%numBody_max ) THEN
                                        ! VERTICAL COORDINATE
                        OBSDAT%REALBODIES%COLUMNS(OBS_PPP)%value_r(IP+IND) &
                                      =PVCORD(ilevel)*vconv +vcordsf(ilem,kidtyp)
                                        !
                                        !  FOR PNM HEIGHT IS SET TO 0
                                        ! ----------------------------
                        IF ( ILEM .EQ. 53 ) THEN
                           OBSDAT%REALBODIES%COLUMNS(OBS_PPP)%value_r(IP+IND)=0.
                        ENDIF
                                        ! ----------------------------
                                        ! CONVERT TO GZ
                        if ( ILEM .EQ. 3 ) then
                           pvalue=GRAV*pvalue
                        endif
                                        ! Max value T-Td upper air
                        IF ( ILEM .EQ. 9 ) THEN
                           IF ( pvalue .GT. ZESMAX) THEN
                              pvalue=ZESMAX
                           ENDIF
                        ENDIF
                                        ! Max value T-Td surface
                        IF ( ILEM .EQ. 11 ) THEN
                           IF ( pvalue .GT. ZESMAX) THEN
                              pvalue=ZESMAX
                           ENDIF
                        ENDIF
                                        ! CONVERT TO RADIANS
                        if ( ILEM .EQ. 48 ) then
                           pvalue=ztorad*pvalue
                        endif
                                        ! FLAGS
                        obsdat%intBodies%columns(OBS_FLG)%value_i(IP+IND) &
                                                      =KFLAGS(ielement,ilevel,IK)

                        OBSDAT%REALBODIES%COLUMNS(OBS_VAR)%value_r(IP+IND)=pvalue
                                        ! initialise o minus p , o minus p6,
                                        ! o minus a, hpht, sigi and sigo to undefined values
                                        ! (-999)
                        obsdat%realBodies%columns(OBS_OMP)%value_r(ip+ind)=-999.
                        obsdat%realBodies%columns(OBS_OMP6)%value_r(ip+ind)=-999.
                        obsdat%realBodies%columns(OBS_OMA)%value_r(ip+ind)=-999.
                        obsdat%realBodies%columns(OBS_HPHT)%value_r(ip+ind)=-999.
                        obsdat%realBodies%columns(OBS_SIGI)%value_r(ip+ind)=-999.
                        obsdat%realBodies%columns(OBS_SIGO)%value_r(ip+ind)=-999.
                        obsdat%intBodies%columns(OBS_VNM)%value_i(IP+IND)=KLIST(ielement)
                        obsdat%intBodies%columns(OBS_VCO)%value_i(IP+IND)=nvcordtyp
                        !
                        !    SURFACE EMISSIVITIES FOR GOES AND AIRS RADIANCES
                        if ( LDAIRS ) then
                           OBSDAT%REALBODIES%COLUMNS(OBS_SEM)%value_r(IP+IND) &
                                                          =PROFIL(ilevel)*ZEMFACT
                        endif
                        ind=ind + 1
                     else
                        kndat = ind
                        obsdat%numBody = obsdat%numBody + kndat
                        return
                     endif
                  endif
               endif
            enddo
         endif
      enddo
      kndat = ind
      obsdat%numBody = obsdat%numBody + kndat

      return
   end subroutine obs_enkf_bdy




   subroutine obs_enkf_prntbdy(obsdat,kstn,kulout) 1,2
      !
      ! object  - print all data records associated with an observation
      !
      !author  : P. Gauthier, C. Charette
      !revision:
      !      P. Houtekamer mrb 2000: reduction and improved readability of output
      !
      !arguments
      !     i   kstn  : no. of station 
      !     i   kulout: unit used for printing
      !
      implicit none

      type(struct_obs), intent(in) :: obsdat
      integer,      intent(in)  :: kstn, kulout

      integer :: ipnt, idata, idata2, jdata, var3d

      ! general information

      ipnt  = obs_headElem_i(obsdat, OBS_RLN, kstn)
      idata = obs_headElem_i(obsdat, OBS_NLV, kstn)

      if(idata.eq.1) then
         write(kulout,fmt=9101)idata,kstn
      else
         write(kulout,fmt=9100)idata,kstn
      end if
9100  format(2x,'there are ',i3,1x,'data in record no.',1x,i6)
9101  format(2x,'there is ',i3,1x,'data in record no.',1x,i6)

      ! print all data records

      write(kulout,'(a,a,a,a)') '   no.   var.  press. ass observ. ', &
         '  o minus p  o minus p6  o minus a    obserr. root(hpht) ', &
         'innov std dev obs std dev   acc ', &
         'zhad  vco'
      do jdata = ipnt, ipnt + idata - 1
         idata2 = jdata -ipnt + 1
         if (btest(obsdat%intBodies%columns(OBS_FLG)%value_i(jdata),12)) then 
            var3d=1
         else
            var3d=0
         endif

         write(kulout,fmt=9201) idata2,obsdat%intBodies%columns(OBS_VNM)%value_i(jdata), &
            obsdat%realBodies%columns(OBS_PPP )%value_r(jdata), &
            obsdat%intBodies%columns(OBS_ASS )%value_i(jdata), &
            obsdat%realBodies%columns(OBS_VAR )%value_r(jdata), &
            obsdat%realBodies%columns(OBS_OMP )%value_r(jdata), &
            obsdat%realBodies%columns(OBS_OMP6)%value_r(jdata), &
            obsdat%realBodies%columns(OBS_OMA )%value_r(jdata), &
            obsdat%realBodies%columns(OBS_OER )%value_r(jdata), &
            obsdat%realBodies%columns(OBS_HPHT)%value_r(jdata), &
            obsdat%realBodies%columns(OBS_SIGI)%value_r(jdata), &
            obsdat%realBodies%columns(OBS_SIGO)%value_r(jdata), &
            var3d, &
            obsdat%realBodies%columns(OBS_ZHA )%value_r(jdata), &
            obsdat%intBodies%columns(OBS_VCO )%value_i(jdata)

      enddo

9201  format(1x,i3,1x,i6,1x,f7.0,1x,i3,8(1x,f10.3),1x,i2, &
         1x,f10.3,1x,i2)

      return

   end subroutine obs_enkf_prntbdy



   subroutine obs_enkf_prnthdr(obsdat,kobs,kulout) 1,19
      !
      ! object  - printing of the header of an observation record
      !
      !author  : P. Gauthier *arma/aes  June 9, 1992
      !revision:
      !     . P. Houtekamer modification of the cma format 
      !arguments
      !     i   kobs  : no. of observation
      !     i   kulout: unit used for optional printing
      !
      implicit none

      type(struct_obs), intent(in) :: obsdat
      integer,      intent(in)  :: kobs, kulout

      ! general information

      write(kulout,fmt=9100)kobs,obsdat%cstnid(KOBS)
9100  format(//,2x,'-- observation record no.' &
         ,1x,i6,3x,' station id:',A12)

      ! print header's content

9202  format(2x,'position within realBodies:',i6)
      write(kulout,fmt=9200) &
         obs_headElem_i(obsdat, OBS_RLN, kobs), &
         obs_headElem_i(obsdat, OBS_ONM, kobs), &
         obs_headElem_i(obsdat, OBS_DAT, kobs), &
         obs_headElem_i(obsdat, OBS_ETM, kobs), &
         obs_headElem_i(obsdat, OBS_INS, kobs), &
         obs_headElem_i(obsdat, OBS_OTP, kobs), &
         obs_headElem_i(obsdat, OBS_ITY, kobs), &
         obs_headElem_r(obsdat, OBS_LAT, kobs), &
         obs_headElem_r(obsdat, OBS_LON, kobs), &
         obs_headElem_r(obsdat, OBS_ALT, kobs), &
         obs_headElem_r(obsdat, OBS_BX , kobs), &
         obs_headElem_r(obsdat, OBS_BY , kobs), &
         obs_headElem_r(obsdat, OBS_BZ , kobs)
      write(kulout,fmt=9201) & 
         obs_headElem_i(obsdat, OBS_NLV, kobs), &
         obs_headElem_i(obsdat, OBS_OFL, kobs), &
         obs_headElem_i(obsdat, OBS_PAS, kobs), &
         obs_headElem_i(obsdat, OBS_REG, kobs), &
         obs_headElem_i(obsdat, OBS_IP , kobs), &
         obs_headElem_i(obsdat, OBS_AZA, kobs)

9200  format(2x,'position within realBodies:',i6,1x,'stn. number:',i6,1x,/, &
         '  date: ',i10,1x,' time: ',i8,/, &
         '  model box:',i12,1x,'instrument: ',i6,1x, &
         'obs. type:',i8,1x,/, &
         '  (lat,lon):',f12.6,1x,f12.6,1x, &
         'stations altitude:',f12.6,1x,/,2x, &
         'block location: ',3(f12.6,1x))
9201  format('  number of data:',i6,1x,'report status: ',i6,1x, &
         ' pass: ',i6,' region: ',i6,/,2x, &
         'processor: ',i6,' azimuth angle: ',i6)

      return
   end subroutine obs_enkf_prnthdr



   subroutine obs_expandToMpiGlobal(obsdat) 1,36
      !
      !**s/r obs_expandToMpiGlobal - restore Global array realBodies and intBodies.
      !
      ! PURPOSE:
      !      To reconstitute the mpi-global observation object by gathering the
      !      necessary data from all processors (to all processors).
      !
      ! NOTE: for the character data cstnid(:), this is converted to integers
      !       with IACHAR and back to characters with ACHAR, to facilitate this
      !       gather through rpn_comm_allreduce
      !
      ! author  : Bin He (ARMA/MRB )
      ! Revision: Mark Buehner - replaced rpn_comm_allreduce with rpn_comm_gather 
      !                          and complete rewrite
      !
      implicit none

      type(struct_obs), intent(inout) :: obsdat

      integer, allocatable :: headerIndex_mpiglobal(:),all_headerIndex_mpiglobal(:,:)
      integer, allocatable :: bodyIndex_mpiglobal(:),all_bodyIndex_mpiglobal(:,:)
      integer, allocatable :: intHeaders_mpilocal(:,:),all_intHeaders_mpilocal(:,:,:)
      real(OBS_REAL), allocatable :: realHeaders_mpilocal(:,:),all_realHeaders_mpilocal(:,:,:)
      integer, allocatable :: intStnid_mpilocal(:,:),all_intStnid_mpilocal(:,:,:)
      integer, allocatable :: intFamily_mpilocal(:,:),all_intFamily_mpilocal(:,:,:)
      integer, allocatable :: intBodies_mpilocal(:,:),all_intBodies_mpilocal(:,:,:)
      real(OBS_REAL), allocatable :: realBodies_mpilocal(:,:),all_realBodies_mpilocal(:,:,:)

      integer :: ierr
      integer :: get_max_rss
      integer :: numHeader_mpilocalmax,numBody_mpilocalmax
      integer :: numHeader_mpiGlobal,numBody_mpiGlobal
      integer :: bodyIndex_mpilocal,bodyIndex
      integer :: headerIndex_mpilocal,headerIndex
      integer :: nsize,sourcePE,nprocs_mpi,myid_mpi
      integer :: charIndex,activeIndex,columnIndex
#if OBS_REAL==4
      character(len=*), parameter :: MPI_OBS_REAL="mpi_real"
#elif OBS_REAL==8
      character(len=*), parameter :: MPI_OBS_REAL="mpi_double_precision"
#endif
      !!---------------------------------------------------------------

      write(*,*) 'Entering obs_expandToMpiGlobal'
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      if(.not. obsdat%mpi_local)then
         call obs_abort('OBS_EXPANDTOMPIGLOBAL has been called, but the ' &
                        // 'obsSpaceData object is already in mpi-global state')
         return
      endif

      ! determine rank and number of mpi tasks
      call rpn_comm_size("GRID",nprocs_mpi,ierr)
      call rpn_comm_rank("GRID",myid_mpi,ierr)

      ! determine number of rows in mpiglobal arrays
      numHeader_mpiGlobal = obs_numHeader_mpiglobal(obsdat)
      numBody_mpiGlobal   = obs_numBody_mpiglobal  (obsdat)

      ! first set the mpiglobal header index value stored in the body table
      do bodyIndex_mpilocal=1,obsdat%numBody
         headerIndex_mpilocal=obs_bodyElem_i(obsdat,OBS_HIND,bodyIndex_mpilocal)
         call obs_bodySet_i(obsdat,OBS_HIND,bodyIndex_mpilocal, &
            obsdat%headerIndex_mpiglobal(headerIndex_mpilocal))
      enddo

      ! gather the lists of mpiglobal header indices on proc 0 to know where everything goes
      call rpn_comm_allreduce(obsdat%numHeader,numHeader_mpilocalmax,1,"mpi_integer","mpi_max","GRID",ierr)
      allocate(headerIndex_mpiglobal(numHeader_mpilocalmax))
      headerIndex_mpiglobal(:)=0
      do headerIndex_mpilocal=1,obsdat%numHeader
         headerIndex_mpiglobal(headerIndex_mpilocal)=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
      enddo
      if(myid_mpi.eq.0) allocate(all_headerIndex_mpiglobal(numHeader_mpilocalmax,0:nprocs_mpi-1))
      call rpn_comm_gather(headerIndex_mpiglobal    ,numHeader_mpilocalmax,"mpi_integer", &
                           all_headerIndex_mpiglobal,numHeader_mpilocalmax,"mpi_integer", &
                           0,"GRID",ierr)
      deallocate(headerIndex_mpiglobal)
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      ! make header-level integer data mpiglobal
      allocate(intHeaders_mpilocal(odc_numActiveColumn(obsdat%intHeaders),numHeader_mpilocalmax))
      intHeaders_mpilocal(:,:)=0
      do headerIndex_mpilocal=1,obsdat%numHeader
         do activeIndex=1,odc_numActiveColumn(obsdat%intHeaders)
            columnIndex=odc_columnIndexFromActiveIndex( &
                                     obsdat%intHeaders%odc_flavour, activeIndex)
            intHeaders_mpilocal(activeIndex,headerIndex_mpilocal)=  &
               obs_headElem_i(obsdat, columnIndex, headerIndex_mpilocal)
         enddo
      enddo
      if(myid_mpi.eq.0) allocate(all_intHeaders_mpilocal(odc_numActiveColumn(obsdat%intHeaders),numHeader_mpilocalmax,0:nprocs_mpi-1))
      nsize=size(intHeaders_mpilocal)
      call rpn_comm_gather(intHeaders_mpilocal    ,nsize,"mpi_integer", &
                           all_intHeaders_mpilocal,nsize,"mpi_integer", &
                           0,"GRID",ierr)
      deallocate(intHeaders_mpilocal)
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      ! make header-level real data mpiglobal
      allocate(realHeaders_mpilocal(odc_numActiveColumn(obsdat%realHeaders),numHeader_mpilocalmax))
      realHeaders_mpilocal(:,:)=real(0.0d0,OBS_REAL)
      do headerIndex_mpilocal=1,obsdat%numHeader
         do activeIndex=1,odc_numActiveColumn(obsdat%realHeaders)
            columnIndex=odc_columnIndexFromActiveIndex( &
                                     obsdat%realHeaders%odc_flavour, activeIndex)
            realHeaders_mpilocal(activeIndex,headerIndex_mpilocal)=  &
               obs_headElem_r(obsdat, columnIndex, headerIndex_mpilocal)
         enddo
      enddo
      if(myid_mpi.eq.0) allocate(all_realHeaders_mpilocal(odc_numActiveColumn(obsdat%realHeaders),numHeader_mpilocalmax,0:nprocs_mpi-1))
      nsize=size(realHeaders_mpilocal)
      call rpn_comm_gather(realHeaders_mpilocal    ,nsize,MPI_OBS_REAL, &
                           all_realHeaders_mpilocal,nsize,MPI_OBS_REAL, &
                           0,"GRID",ierr)
      deallocate(realHeaders_mpilocal)
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      ! make station-id data mpiglobal
      allocate(intStnid_mpilocal(len(obsdat%cstnid(1)),numHeader_mpilocalmax))
      intStnid_mpilocal(:,:)=0
      do headerIndex_mpilocal=1,obsdat%numHeader
         do charIndex=1,len(obsdat%cstnid(1))
            intStnid_mpilocal(charIndex,headerIndex_mpilocal)=  &
               iachar(obsdat%cstnid(headerIndex_mpilocal)(charIndex:charIndex))
         enddo
      enddo
      if(myid_mpi.eq.0) allocate(all_intStnid_mpilocal(len(obsdat%cstnid(1)),numHeader_mpilocalmax,0:nprocs_mpi-1))
      nsize=size(intStnid_mpilocal)
      call rpn_comm_gather(intStnid_mpilocal    ,nsize,"mpi_integer", &
                           all_intStnid_mpilocal,nsize,"mpi_integer", &
                           0,"GRID",ierr)
      deallocate(intStnid_mpilocal)
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      ! make obs family data mpiglobal
      allocate(intFamily_mpilocal(len(obsdat%cfamily(1)),numHeader_mpilocalmax))
      intFamily_mpilocal(:,:)=0
      do headerIndex_mpilocal=1,obsdat%numHeader
         do charIndex=1,len(obsdat%cfamily(1))
            intFamily_mpilocal(charIndex,headerIndex_mpilocal)=  &
               iachar(obsdat%cfamily(headerIndex_mpilocal)(charIndex:charIndex))
         enddo
      enddo
      if(myid_mpi.eq.0) allocate(all_intFamily_mpilocal(len(obsdat%cfamily(1)),numHeader_mpilocalmax,0:nprocs_mpi-1))
      nsize=size(intFamily_mpilocal)
      call rpn_comm_gather(intFamily_mpilocal    ,nsize,"mpi_integer", &
                           all_intFamily_mpilocal,nsize,"mpi_integer", &
                           0,"GRID",ierr)
      deallocate(intFamily_mpilocal)
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      ! gather the lists of mpiglobal body indices on proc 0 to know where everything goes
      call rpn_comm_allreduce(obsdat%numBody,numBody_mpilocalmax,1,"mpi_integer","mpi_max","GRID",ierr)
      allocate(bodyIndex_mpiglobal(numBody_mpilocalmax))
      bodyIndex_mpiglobal(:)=0
      do bodyIndex_mpilocal=1,obsdat%numBody
         bodyIndex_mpiglobal(bodyIndex_mpilocal)=obsdat%bodyIndex_mpiglobal(bodyIndex_mpilocal)
      enddo
      if(myid_mpi.eq.0) allocate(all_bodyIndex_mpiglobal(numBody_mpilocalmax,0:nprocs_mpi-1))
      call rpn_comm_gather(bodyIndex_mpiglobal    ,numBody_mpilocalmax,"mpi_integer", &
                           all_BodyIndex_mpiglobal,numBody_mpilocalmax,"mpi_integer", &
                           0,"GRID",ierr)
      deallocate(bodyIndex_mpiglobal)
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      ! make body-level integer data mpiglobal
      allocate(intBodies_mpilocal(odc_numActiveColumn(obsdat%intBodies),numBody_mpilocalmax))
      intBodies_mpilocal(:,:)=0
      do bodyIndex_mpilocal=1,obsdat%numBody
         do activeIndex=1,odc_numActiveColumn(obsdat%intBodies)
            columnIndex=odc_columnIndexFromActiveIndex( &
                                     obsdat%intBodies%odc_flavour, activeIndex)
            intBodies_mpilocal(activeIndex,bodyIndex_mpilocal)=  &
               obs_bodyElem_i(obsdat, columnIndex, bodyIndex_mpilocal)
         enddo
      enddo
      if(myid_mpi.eq.0) allocate(all_intBodies_mpilocal(odc_numActiveColumn(obsdat%intBodies),numBody_mpilocalmax,0:nprocs_mpi-1))
      nsize=size(intBodies_mpilocal)
      call rpn_comm_gather(intBodies_mpilocal    ,nsize,"mpi_integer", &
                           all_intBodies_mpilocal,nsize,"mpi_integer", &
                           0,"GRID",ierr)
      deallocate(intBodies_mpilocal)
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      ! make body-level real data mpiglobal
      allocate(realBodies_mpilocal(odc_numActiveColumn(obsdat%realBodies),numBody_mpilocalmax))
      realBodies_mpilocal(:,:)=real(0.0d0,OBS_REAL)
      do bodyIndex_mpilocal=1,obsdat%numBody
         do activeIndex=1,odc_numActiveColumn(obsdat%realBodies)
            columnIndex=odc_columnIndexFromActiveIndex( &
                                     obsdat%realBodies%odc_flavour, activeIndex)
            realBodies_mpilocal(activeIndex,bodyIndex_mpilocal)=  &
               obs_bodyElem_r(obsdat, columnIndex, bodyIndex_mpilocal)
         enddo
      enddo
      if(myid_mpi.eq.0) allocate(all_realBodies_mpilocal(odc_numActiveColumn(obsdat%realBodies),numBody_mpilocalmax,0:nprocs_mpi-1))
      nsize=size(realBodies_mpilocal)
      call rpn_comm_gather(realBodies_mpilocal    ,nsize,MPI_OBS_REAL, &
                           all_realBodies_mpilocal,nsize,MPI_OBS_REAL, &
                           0,"GRID",ierr)
      deallocate(realBodies_mpilocal)
      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      ! destroy object's mpilocal data and allocate mpiglobal data
      call obs_deallocate(obsdat)

      ! Only processor 0 does any work hereafter
      if(myid_mpi.eq.0) then
          call obs_allocate(obsdat,numHeader_mpiGlobal,numBody_mpiGlobal)
      else
          call obs_allocate(obsdat,0,0)
      endif

      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'

      if(myid_mpi.eq.0) then

         do sourcePE=0,nprocs_mpi-1
            do headerIndex_mpilocal=1,numHeader_mpilocalmax
               ! grab the mpiglobal header index
               headerIndex=all_headerIndex_mpiglobal(headerIndex_mpilocal,sourcePE)
               if(headerIndex.gt.0) then

                  do activeIndex=1,odc_numActiveColumn(obsdat%realHeaders)
                     columnIndex=odc_columnIndexFromActiveIndex(obsdat%realHeaders%odc_flavour,activeIndex)
                     obsdat%realHeaders%columns(columnIndex)%value_r(headerIndex)= &
                            all_realHeaders_mpilocal(activeIndex,headerIndex_mpilocal,sourcePE)
                  enddo

                  do activeIndex=1,odc_numActiveColumn(obsdat%intHeaders)
                     columnIndex=odc_columnIndexFromActiveIndex(obsdat%intHeaders%odc_flavour,activeIndex)
                     obsdat%intHeaders%columns(columnIndex)%value_i(headerIndex)= &
                            all_intHeaders_mpilocal(activeIndex,headerIndex_mpilocal,sourcePE)
                  enddo

                  do charIndex=1,len(obsdat%cstnid(1))
                     obsdat%cstnid(headerIndex)(charIndex:charIndex) = &
                       achar(all_intStnid_mpilocal(charIndex,headerIndex_mpilocal,sourcePE))
                  enddo

                  do charIndex=1,len(obsdat%cfamily(1))
                     obsdat%cfamily(headerIndex)(charIndex:charIndex) = &
                        achar(all_intFamily_mpilocal(charIndex,headerIndex_mpilocal,sourcePE))
                  enddo

               endif
            enddo
         enddo

         do sourcePE=0,nprocs_mpi-1
            do bodyIndex_mpilocal=1,numBody_mpilocalmax
               bodyIndex=all_bodyIndex_mpiglobal(bodyIndex_mpilocal,sourcePE)
               if(bodyIndex.gt.0) then

                  do activeIndex=1,odc_numActiveColumn(obsdat%realBodies)
                     columnIndex=odc_columnIndexFromActiveIndex(obsdat%realBodies%odc_flavour,activeIndex)
                     obsdat%realBodies%columns(columnIndex)%value_r(bodyIndex)= &
                        all_realBodies_mpilocal(activeIndex,bodyIndex_mpilocal,sourcePE)
                  enddo

                  do activeIndex=1,odc_numActiveColumn(obsdat%intBodies)
                     columnIndex=odc_columnIndexFromActiveIndex(obsdat%intBodies%odc_flavour,activeIndex)
                     obsdat%intBodies%columns(columnIndex)%value_i(bodyIndex)=  &
                        all_intBodies_mpilocal(activeIndex,bodyIndex_mpilocal,sourcePE)
                  enddo

               endif
            enddo
         enddo
 

         ! Make RLN point to global data
         do headerIndex=1,numHeader_mpiGlobal
            if(headerIndex == 1) then
               obsdat%intHeaders%columns(OBS_RLN)%value_i(headerIndex) = 1
            else
               obsdat%intHeaders%columns(OBS_RLN)%value_i(headerIndex) = &
                   obsdat%intHeaders%columns(OBS_RLN)%value_i(headerIndex-1) + &
                   obsdat%intHeaders%columns(OBS_NLV)%value_i(headerIndex-1)
            endif
         enddo

         obsdat%numBody     =  numBody_mpiGlobal
         obsdat%numHeader   =numHeader_mpiGlobal

         ! deallocate the complete temporary arrays
         deallocate(all_headerIndex_mpiglobal)
         deallocate(all_bodyIndex_mpiglobal)
         deallocate(all_intStnid_mpilocal)
         deallocate(all_intFamily_mpilocal)
         deallocate(all_intHeaders_mpilocal)
         deallocate(all_realHeaders_mpilocal)
         deallocate(all_intBodies_mpilocal)
         deallocate(all_realBodies_mpilocal)

      endif ! myid_mpi.eq.0

      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
      obsdat%mpi_local = .false.
      write(*,*) 'Leaving obs_expandToMpiGlobal'
      return
   end subroutine obs_expandToMpiGlobal



   subroutine obs_finalize(obsdat) 5,1
      !s/r obs_finalize - De-allocate memory and clean up the object.
      !
      ! PURPOSE:
      !      De-allocate object arrays, and perform any other clean-up that is
      !      necessary before object deletion.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs), intent(inout) :: obsdat

      call obs_deallocate(obsdat)
   end subroutine obs_finalize



   subroutine obs_generate_header(obsdat, ilat, ilon, ialt, inbon, instrum, &,19
                                  isatzen, isat, itech, nvtyp, ity, idate, &
                                  itime, clstnid, imask, isatazim, isunza, iclfr)
      !
      !    OUTPUT:
      !           obsdat%realHeaders%columns(OBS_LON,) - in degrees
      !           obsdat%realHeaders%columns(OBS_LAT,) - in degrees, equator at 0 degrees
      !           obsdat%realHeaders%columns(OBS_ALT,) - in metres, with no offset
      !
      use MathPhysConstants_mod
      implicit none

      type(struct_obs), intent(inout) :: obsdat
      integer, intent(in) :: ilat, ilon, ialt, inbon, instrum, isatzen
      integer, intent(in) :: isat, itech, nvtyp, ity, idate, itime
      integer, intent(in) :: imask, isatazim, isunza, iclfr
      character(len=9), intent(in) :: clstnid
      real(kind=8) :: torad

      torad=MPC_RADIANS_PER_DEGREE_R8

      !
      !     IF VALID DATA WERE FOUND GENERATE THE OBSDAT HEADER
      !      AND INCREMENT OBSDAT%numHeader
      !
      ! PLH          if  ( obsdat%numHeader .lt. nmxobs) then
      if ( obsdat%numHeader .lt. obsdat%numHeader_max) then
         obsdat%numHeader=obsdat%numHeader + 1

         obsdat%realHeaders%columns(OBS_LON)%value_r(obsdat%numHeader) = real(ilon) *0.01
         obsdat%realHeaders%columns(OBS_LAT)%value_r(obsdat%numHeader) = real(ilat) *0.01-90.0
         ! PLH ADDED OBS_BX OBS_BY OBS_BZ
         obsdat%realHeaders%columns(OBS_BX)%value_r(obsdat%numHeader)=0.0
         obsdat%realHeaders%columns(OBS_BY)%value_r(obsdat%numHeader)=0.0
         obsdat%realHeaders%columns(OBS_BZ)%value_r(obsdat%numHeader)=0.0

         obsdat%realHeaders%columns(OBS_ALT)%value_r(obsdat%numHeader) = real(ialt)
         ! PLH       obsdat%realHeaders%columns(ncmtlo)%value_r(obsdat%numHeader) = (real(ilon)*0.01)*ztorad
         ! PLH       obsdat%realHeaders%columns(ncmtla)%value_r(obsdat%numHeader) = (real(ilat)*0.01-90.)*ztorad
         call obs_headSet_i(obsdat, OBS_NLV, obsdat%numHeader, inbon)
         !       print*,'NOBTOTAL=',obsdat%numHeader

         if ( obsdat%numHeader .eq. 1) then
            ! This is the first entry into the obsdat
            call obs_headSet_i(obsdat, OBS_RLN, 1, 1)
         else
            call obs_headSet_i(obsdat, OBS_RLN, obsdat%numHeader, &
                obs_headElem_i(obsdat, OBS_RLN, obsdat%numHeader-1) &
              + obs_headElem_i(obsdat, OBS_NLV, obsdat%numHeader-1))
         endif
         !
         !          REMAINDER OF HEADER
         !
         call obs_headSet_i(obsdat, OBS_ONM, obsdat%numHeader, obsdat%numHeader)
         call obs_headSet_i(obsdat, OBS_INS, obsdat%numHeader, instrum )
         call obs_headSet_i(obsdat, OBS_SZA, obsdat%numHeader, isatzen)
         call obs_headSet_i(obsdat, OBS_SAT, obsdat%numHeader, isat)
         call obs_headSet_i(obsdat, OBS_TEC, obsdat%numHeader, itech)
         call obs_headSet_i(obsdat, OBS_OTP, obsdat%numHeader, nvtyp)
         call obs_headSet_i(obsdat, OBS_ITY, obsdat%numHeader, ity)
         call obs_headSet_i(obsdat, OBS_DAT, obsdat%numHeader, idate)
         call obs_headSet_i(obsdat, OBS_ETM, obsdat%numHeader, itime)
         obsdat%cstnid(obsdat%numHeader)         = clstnid
         ! PLH       call obs_headSet_i(obsdat, ncmoec, obsdat%numHeader, 999)
         call obs_headSet_i(obsdat, OBS_OFL, obsdat%numHeader, imask)
         call obs_headSet_i(obsdat, OBS_AZA, obsdat%numHeader, isatazim)
         call obs_headSet_i(obsdat, OBS_SUN, obsdat%numHeader, isunza)
         call obs_headSet_i(obsdat, OBS_CLF, obsdat%numHeader, iclfr)
         ! PLH       call obs_headSet_i(obsdat, ncmst1, obsdat%numHeader, iflgs)
      endif

   end subroutine obs_generate_header



   integer function obs_get_obs_index_for_bufr_element(kbufrn) 3
      implicit none
      integer, intent(in) :: kbufrn
      !
      !      PURPOSE: TO FIND THE INDEX OF THE OBSDAT VARIABLE TYPES LIST ELEMENT
      !               THAT CONTAINS A BUFR ELEMENT NUMBER
      !
      !    ARGUMENTS:
      !               INPUT:
      !                   -KBUFRN: THE BUFR CLASSIFICATION ELEMENT NUMBER
      !                            i.e. known locally as the 'burp variable type'
      !                            i.e. table B of the ECMWF BUFR reference
      !                            BUFR = Binary Universal Form for the
      !                                   Representation of meteorological data
      !
      !               OUTPUT:
      !                   - obs_get_obs_index_for_bufr_element:
      !                            THE FOUND INDEX (=-1 IF NOT FOUND)
      !
      !       AUTHOR: P. KOCLAS (CMC TEL. 4665)

      integer indbuf
      integer, parameter, dimension(OBS_JPNBRELEM) :: NVNUMB = (/ &
         011003, 011004, 010194, 010192,     29, & !  1-10
         013208, 012063, 012001, 012192, 012004, &
         012203, 011215, 011216, 013210, 013220, & ! 11-20
         62, 015001,     64,     015037, 015036, &
         015031, 015032,     69,     70,     71, & ! 21-30
         72,     73,     74,     75,     76, &
         77,     78,     79,     80,     81, & ! 31-40
         82,     83,     84,     85,     86, &
         87,     88,     89,     90,     91, & ! 41-50
         012163, 010004, 011001, 011002, 012062, &
         008001, 008004, 010051, 011011, 011012, & ! 51-57
         41,     42 /)


      ! OBS. ARRAY VARIABLES NUMBERING IN A BURP FILE
      !  Descriptions taken from 3d variational code(March 2011, revision 11.0.2)
      !
      !  1 =011003 (U COMPONENT)           (m/s)
      !  2 =011004 (V COMPONENT)           (m/s)
      !  3 =010194 (GEOPOTENTIAL IN J/KG)   (z metres)
      !  4 =010192 (THICKNESS IN M)
      !  5 =    29 (RELATIVE HUMIDITY)
      !  6 =013208
      !  7 =012063 BRIGHTNESS TEMPERATURE 1
      !  8 =012001 (TEMPERATURE)            (kelvin)
      !  9 =012192  (DEW-POINT DEPRESSION)              (t-td kelvin)
      ! 10 =012004 (2M TEMPERATURE)
      ! 11 =012203 (2M DEW-POINT DEPRESSION)
      ! 12 =011215 SURFACE U     WIND COMPONENT M/S)
      ! 13 =011216 SURFACE V N-S WIND COMPONENT M/S)
      ! 14 =013210 (NAPIERIAN LOGARITHM OF SPECIFIC HUMIDITY) LN(KG/KG)
      ! 15 =013220 (NAPIERIAN LOGARITHM OF 2M SPECIFIC HUMIDITY) LN(KG/KG)
      ! 16 =007006 HEIGHT ABOVE STATION (M)
      ! 17 =015001 (Total Ozone from TOVS)
      ! 18 =    64 (CM)
      ! 19 =015037 (GPSRO BENDING ANGLE)
      ! 20 =015036 (GPSRO REFRACTIVITY)
      ! 21 =015031 (GPSGB ZTD IN M)
      ! 22 =015032 (GPSGB ZTD ERROR IN M)
      ! 23 =    69 (C)
      ! 24 =    70 (NS)
      ! 25 =    71 (S)
      ! 26 =    72 (E)
      ! 27 =    73 (TGTG)
      ! 28 =    74 (SPSP)
      ! 29 =    75 (SPSP)
      ! 30 =    76 (RS)
      ! 31 =    77 (ESES)
      ! 32 =    78 (IS)
      ! 33 =    79 (TRTR)
      ! 34 =    80 (RR)
      ! 35 =    81 (JJ)
      ! 36 =    82 (VS)
      ! 37 =    83 (DS)
      ! 38 =    84 (HWHW)
      ! 39 =    85 (PWPW)
      ! 40 =    86 (DWDW)
      ! 41 =    87 (GENERAL CLOUD GROUP)
      ! 42 =    88 (RH FROM LOW CLOUDS)
      ! 43 =    89 (RH FROM MIDDLE CLOUDS)
      ! 44 =    90 (RH FROM HIGH CLOUDS)
      ! 45 =    91 (TOTAL AMOUNT OF CLOUDS)
      ! 46 =012163 (TOVS LEVEL 1B RADIANCES)
      ! 47 =010004(PRESSURE (VERT COORDINATE=Z))   (pascals)
      ! 48 =011001(DD (WIND DIRECTION IN RADIANS)) (degrees)
      ! 49 =011002(FF (WIND SPEED))                (m/s)
      ! 50 =012062 (RAW RADIANCE (BRIGHTNESS TEMPERATURE IN K)
      ! 51 =008001
      ! 52 =008004
      ! 53 =010051
      ! 54 =011011
      ! 55 =011012
      ! 56 =    41 (U AT 10M)
      ! 57 =    42 (V AT 10M)

      obs_get_obs_index_for_bufr_element=-1
      do indbuf=1,OBS_JPNBRELEM
         if (NVNUMB(indbuf) .eq. kbufrn ) then
            obs_get_obs_index_for_bufr_element=indbuf
            return
         endif
      enddo

      return
   end function obs_get_obs_index_for_bufr_element



   function obs_getBodyIndex_depot(obsdat) result(row_index) 1
      !
      ! PURPOSE:
      !      Return the next element from the current body list
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      integer :: row_index
      type(struct_obs), intent(inout) :: obsdat

      row_index = ild_get_next_index(obsdat%body_index_list_depot)
   end function obs_getBodyIndex_depot



   function obs_getBodyIndex_private(private_list) result(row_index) 1
      !
      ! PURPOSE:
      !      Return the next element from the supplied private body list
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      integer :: row_index
      type(struct_index_list), pointer, intent(inout) :: private_list

      row_index = ild_get_next_index(private_list)
   end function obs_getBodyIndex_private



   function obs_getFamily(obsdat,headerIndex_in,bodyIndex) 10,2
      !
      ! PURPOSE:
      !      Return the family for the indicated header, or else for the
      !      indicated body.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none

      character(len=2)             :: obs_getFamily
      type(struct_obs), intent(in) :: obsdat
      integer,optional, intent(in) :: headerIndex_in,bodyIndex

      integer          :: headerIndex

      if(present(headerIndex_in)) then
         headerIndex=headerIndex_in
      elseif(present(bodyIndex)) then
         headerIndex=obs_bodyElem_i(obsdat,OBS_HIND,bodyIndex)
      else
         call obs_abort('OBS_GETFAMILY: Header or Body index must be specified!')
         return
      endif

      obs_getFamily=obsdat%cfamily(headerIndex)

   end function obs_getFamily



   function obs_getNclassAvhrr()  1
      ! PURPOSE:
      !      to get the number of  AVHRR radiance classes
      !
      ! author  : S. Heilliette - 2013
      !
     implicit none

     integer :: obs_getNclassAvhrr

     obs_getNclassAvhrr = ( OBS_CF7 - OBS_CF1 + 1 )

   end function obs_getNclassAvhrr


   function obs_getNchanAvhrr()  1
      ! PURPOSE:
      !      to get the number of AVHRR channels
      !
      ! author  : S. Heilliette - 2013
      !
     implicit none

     integer :: obs_getNchanAvhrr

     obs_getNchanAvhrr = ( OBS_M1C6 - OBS_M1C1 + 1 )

   end function obs_getNchanAvhrr


   function obs_getHeaderIndex(obsdat) result(row_index) 37
      !
      ! PURPOSE:
      !      Return the next element from the current header list.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      integer :: row_index
      type(struct_obs), intent(inout) :: obsdat

      row_index = ild_get_next_index(obsdat%header_index_list_depot)
   end function obs_getHeaderIndex



   function obs_headElem_i(obsdat,column_index,row_index) result(value_i) 267,1
      !func obs_headElem_i -Get an integer-valued header observation-data element
      !
      ! PURPOSE:
      !      To control access to the observation object.  Returns the (integer)
      !      value of the row_index'th ObsData element with the indicated column
      !      index from the "header".
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      integer                       :: value_i
      type(struct_obs), intent(in)  :: obsdat
      integer         , intent(in)  :: column_index
      integer         , intent(in)  :: row_index

      real(OBS_REAL) :: value_r         ! not used

      call odc_columnElem(obsdat%intHeaders, column_index, row_index, &
                          value_i, value_r)
   end function obs_headElem_i



   function obs_headElem_r(obsdat,column_index,row_index) result(value_r) 106,1
      !func obs_headElem_r - Get a real-valued header observation-data element
      !
      ! PURPOSE:
      !      To control access to the observation object.  Returns the (real)
      !      value of the row_index'th ObsData element with the indicated column
      !      index from the "header".
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      real(OBS_REAL)                :: value_r
      type(struct_obs), intent(in)  :: obsdat
      integer         , intent(in)  :: column_index
      integer         , intent(in)  :: row_index

      integer :: value_i                ! unused

      call odc_columnElem(obsdat%realHeaders, column_index, row_index, &
                          value_i, value_r)
   end function obs_headElem_r



   function obs_headerIndex_mpiglobal(obsdat,row_index) result(value)
      !func obs_headerIndex_mpiglobal - Get the mpiglobal header row index
      !
      ! PURPOSE:
      !      To control access to the mpiglobal row_index into the "header".
      !
      ! author  : M. Buehner - 2012
      !
      implicit none
      integer value
      type(struct_obs), intent(in)  :: obsdat
      integer         , intent(in)  :: row_index

      value=obsdat%headerIndex_mpiglobal(row_index)
   end function obs_headerIndex_mpiglobal



   subroutine obs_headSet_i(obsdat, column_index, row_index, value_i) 80,1
      !s/r obs_headSet_i - set an integer-valued header observation-data element
      !
      ! PURPOSE:
      !      To control access to the observation object.  Sets the (integer)
      !      value of the row_index'th ObsData element with the indicated column
      !      index from the "header".
      !
      ! author  : J.W. Blezius and M. Buehner - 2012
      !
      implicit none
      type(struct_obs), intent(inout)  :: obsdat
      integer         , intent(in)     :: column_index
      integer         , intent(in)     :: row_index
      integer         , intent(in)     :: value_i

      call odc_columnSet(obsdat%intHeaders, column_index, row_index, &
                         value_i, NULL_COLUMN_VALUE_R, &
                         obsdat%numHeader, obsdat%numHeader_max)
   end subroutine obs_headSet_i



   subroutine obs_headSet_r(obsdat, column_index, row_index, value_r) 25,1
      !s/r obs_headSet_r - set a real header value in the observation object
      !
      ! PURPOSE:
      !      To control access to the observation object.  Sets the (real)
      !      value of the row_index'th ObsData element with the indicated column
      !      index from the "header".
      !
      ! author  : J.W. Blezius and M. Buehner - 2012
      !
      implicit none
      type(struct_obs), intent(inout)  :: obsdat
      integer         , intent(in)     :: column_index
      integer         , intent(in)     :: row_index
      real(OBS_REAL)  , intent(in)     :: value_r

      call odc_columnSet(obsdat%realHeaders, column_index, row_index, &
                         NULL_COLUMN_VALUE_I, value_r, &
                         obsdat%numHeader, obsdat%numHeader_max)
   end subroutine obs_headSet_r



   subroutine obs_initialize(obsdat, numHeader_max, numBody_max, mpi_local, & 2,5
                             silent)
      !s/r obs_initialize - Set an observation-data module to a known state.
      !
      ! PURPOSE:
      !      Initialize object variables, and allocate arrays according to the
      !      parameters, header_max and body_max.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
                                        ! instance of obsSpaceData
      type(struct_obs),intent(inout):: obsdat !inout allows detection of 2nd call
                                        ! number of header elements allocated
      integer, optional, intent(in) :: numHeader_max
                                        ! total no. of body elements allocated
      integer, optional, intent(in) :: numBody_max
      logical, optional, intent(in) :: mpi_local
      logical, optional, intent(in) :: silent

      logical :: silent_
      integer :: nulnam,fnom,fclos,ierr
      integer :: nmxobs,ndatamx
      namelist /namdimo/nmxobs,ndatamx
      character(len=120) :: message

      if(.not. obs_class_initialized) then
         call obs_abort('obs_class_initialize must be called before ' // &
                        'obs_initialize')
         return
      end if

      !
      ! INITIALIZE ALL OBJECT VARIABLES
      !
      nullify(obsdat%cstnid)
      nullify(obsdat%cfamily)

      obsdat%numHeader     = 0
      obsdat%numHeader_max = 0
      obsdat%numBody       = 0
      obsdat%numBody_max   = 0

      if(present(mpi_local)) then
         obsdat%mpi_local  = mpi_local
      else
         obsdat%mpi_local  = .false.
      end if

      if(present(silent)) then
         silent_  = silent
      else
         silent_  = .false.
      end if

      nullify(obsdat%headerIndex_mpiglobal)
      nullify(obsdat%bodyIndex_mpiglobal)

      !
      ! DETERMINE THE ARRAY DIMENSIONS
      !
      if(present(numHeader_max)) then
         ! numBody_max is necessarily also present
         nmxobs = numHeader_max
         ndatamx = numBody_max

      else
         ! Initialize with bad values
         nmxobs=0
         ndatamx=0

         ! Open the file, flnml
         nulnam=0
         ierr=fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
         if(ierr < 0) then
            write(message,*)'Failed to open flnml to obtain nmxobs and ndatamx:'&
                            // '  ierr=', ierr
            call obs_abort(message); return
         end if

         ! Read the dimensions from a namelist
         read(nulnam,nml=namdimo,iostat=ierr)
         if(ierr.ne.0) call obs_abort('obs_initialize: Error reading namelist')
         write(*,nml=namdimo)
         ierr=fclos(nulnam)
         ! Verify that the namelist contained values
         if(nmxobs <= 0 .or. ndatamx <= 0) then
            write(message,*)'From file, flnml, positive values were not ' &
                            // 'obtained for nmxobs or ndatamx:  ',nmxobs,ndatamx
            call obs_abort(message); return
         end if
      end if ! present(numHeader_max)

      !
      ! ALLOCATE
      !
      call obs_allocate(obsdat, nmxobs, ndatamx, silent_)

      return
   end subroutine obs_initialize



   subroutine obs_mpiDistributeIndices(obsdat) 1,4
      !
      ! PURPOSE:
      !  Compute headerIndex_mpiglobal and bodyIndex_mpiglobal: 
      !  this determines how obs are distributed over MPI processes
      !  and is needed for converting from mpiglobal to mpilocal and vice versa.
      !  The header indices are distributed following the chosen strategy,
      !  currently either "round robin" or by latitude bands.
      !
      !  Note: this subroutine is called before converting from mpiglobal to 
      !        mpilocal
      !
      !Author  : Bin He *ARMA/MRB  Feb. 2009
      !
      !Revision:
      !  
      !Arguments: none
      !
      !Comments:  In principle this method could have obtained
      !           my_mpi_id by use'ing the module, mpi.  However, it queries
      !           rpn_comm for itself because the mpi module belongs to the
      !           3dvar code, whereas the present module is shared code.
      !
      implicit none
      type(struct_obs), intent(inout) :: obsdat

      integer :: headerIndex_mpiglobal,headerIndex_mpilocal
      integer ::   bodyIndex_mpiglobal,  bodyIndex_mpilocal
      integer :: numHeader_mpiLocal,numBody_mpiLocal,idata,idataend
      integer :: my_mpi_id, my_mpi_idx_dummy, my_mpi_idy_dummy, ierr
      character(len=100) :: message

      write(*,*) '-------- Start obs_mpiDistributeIndices ---------'

      if(obsdat%mpi_local) then
         call obs_abort( &
                      'obs_mpiDistributeIndices: data already mpi-local, Abort!')
         return
      end if

      call rpn_comm_mype(my_mpi_id, my_mpi_idx_dummy, my_mpi_idy_dummy)

      ! Count number of headers and bodies for each processor
      numHeader_mpiLocal=0
      numBody_mpiLocal=0
      do headerIndex_mpiglobal=1,obsdat%numHeader
         if(my_mpi_id.eq.obs_headElem_i(obsdat,OBS_IP,headerIndex_mpiglobal))then
            numHeader_mpiLocal=numHeader_mpiLocal+1
            numBody_mpilocal=numBody_mpilocal &
                            +obs_headElem_i(obsdat,OBS_NLV,headerIndex_mpiglobal)
         end if
      enddo
      write(*,*) 'obs_mpidistributeindices: numHeader_mpiLocal,global=',  &
                 numHeader_mpiLocal,obsdat%numHeader
      write(*,*) 'obs_mpidistributeindices: numBody_mpiLocal,global=',  &
                 numBody_mpiLocal,obsdat%numBody

      if(numHeader_mpilocal.gt.0) then
         ! Allocate the list of global header indices
         allocate(obsdat%headerIndex_mpiglobal(numHeader_mpilocal))
         obsdat%headerIndex_mpiglobal(:)=0
      else
         nullify(obsdat%headerIndex_mpiglobal)
         write(*,*) 'This mpi processor has zero headers.'
      end if

      if(numBody_mpilocal.gt.0) then
         ! Allocate the list of global body indices
         allocate(obsdat%bodyIndex_mpiglobal(numBody_mpilocal))
         obsdat%bodyIndex_mpiglobal(:)=0
      else
         nullify(obsdat%bodyIndex_mpiglobal)
         write(*,*) 'This mpi processor has zero bodies to treat'
      endif

      ! determine the list of header indices
      headerIndex_mpilocal=0
      do headerIndex_mpiglobal=1,obsdat%numHeader
         if(my_mpi_id.eq.obs_headElem_i(obsdat,OBS_IP,headerIndex_mpiglobal))then
            headerIndex_mpilocal=headerIndex_mpilocal+1
            obsdat%headerIndex_mpiglobal(headerIndex_mpilocal) &
                                                           =headerIndex_mpiglobal
         endif
      enddo

      ! determine the corresponding list of body indices
      bodyIndex_mpilocal=0
      do headerIndex_mpilocal=1,numHeader_mpilocal
         headerIndex_mpiglobal=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
         idata= obs_headElem_i(obsdat, OBS_RLN, headerIndex_mpiglobal)
         idataend = obs_headElem_i(obsdat, OBS_NLV, headerIndex_mpiglobal) &
                  + idata -1 
         do bodyIndex_mpiglobal=idata,idataend 
            bodyIndex_mpilocal=bodyIndex_mpilocal+1 
            obsdat%bodyIndex_mpiglobal(bodyIndex_mpilocal) = bodyIndex_mpiglobal
         enddo
      enddo

      write(*,*) '-------- END OF obs_mpiDistributeIndices ---------'
      write(*,*) ' '
   end subroutine obs_mpiDistributeIndices



   logical function obs_mpiLocal(obsdat)
      !func obs_mpiLocal - returns true if the object contains only data that are
      !                    needed by the current mpi PE; false if it contains all
      !                    data.
      !
      ! PURPOSE:
      !      To provide the state of the internal variable, mpiLocal.  This
      !      method exists primarily to facilitate unit tests on this module.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs) , intent(in)  :: obsdat
      obs_mpiLocal=obsdat%mpi_local
   end function obs_mpiLocal



   integer function obs_numBody(obsdat) 39
      !func obs_numBody - returns the number of mpi-local bodies recorded
      !
      ! PURPOSE:
      !      To provide the number of bodies that are currently recorded in the
      !      mpi-local observation-data object.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs) , intent(in)  :: obsdat
      obs_numBody=obsdat%numBody
   end function obs_numBody



   integer function obs_numBody_max(obsdat) 1
      !func obs_numBody_max - returns the dimensioned mpi-local number of bodies
      !
      ! PURPOSE:
      !      To provide the dimension for the number of bodies in the mpi-local
      !      observation-data object.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs) , intent(in)  :: obsdat
      obs_numBody_max=obsdat%numBody_max
   end function obs_numBody_max



   integer function obs_numBody_mpiglobal(obsdat) 1
      !func obs_numBody_mpiglobal - returns the number of bodies recorded in the
      !                             entire mpi-global obs object
      !
      ! PURPOSE:
      !      To provide the number of bodies that are currently recorded in the
      !      entire mpi-global observation-data object.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs), intent(in)  :: obsdat
      integer :: numBody_mpiGlobal, sizedata, ierr

      if(obsdat%mpi_local)then
         sizedata=1
         call rpn_comm_allreduce(obsdat%numBody,numBody_mpiGlobal,sizedata, &
                                 "mpi_integer","mpi_sum","GRID",ierr)
         obs_numBody_mpiglobal = numBody_mpiGlobal
      else
         obs_numBody_mpiglobal = obsdat%numBody
      end if

   end function obs_numBody_mpiglobal



   integer function obs_numHeader(obsdat) 39
      !func obs_numHeader - returns the number of mpi-local headers recorded
      !
      ! PURPOSE:
      !      To provide the number of headers that are currently recorded in the
      !      observation-data object.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs) , intent(in)  :: obsdat
      obs_numHeader=obsdat%numHeader
   end function obs_numHeader



   integer function obs_numHeader_max(obsdat) 1
      !func obs_numHeader_max - returns the dimensioned mpi-local number of
      !                         headers
      !
      ! PURPOSE:
      !      To provide the dimension for the number of headers in the mpi-local
      !      observation-data object.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs) , intent(in)  :: obsdat
      obs_numHeader_max=obsdat%numHeader_max
   end function obs_numHeader_max



   integer function obs_numHeader_mpiglobal(obsdat) 1
      !func obs_numHeader_mpiglobal - returns the number of headers recorded in
      !                               the entire mpi-global obs object
      !
      ! PURPOSE:
      !      To provide the number of headers that are currently recorded in the
      !      entire mpi-global observation-data object.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs) , intent(in)  :: obsdat
      integer :: numHeader_mpiGlobal, sizedata, ierr

      if(obsdat%mpi_local)then
         sizedata=1
         call rpn_comm_allreduce(obsdat%numHeader,numHeader_mpiGlobal,sizedata, &
                                 "mpi_integer","mpi_sum","GRID",ierr)
         obs_numHeader_mpiglobal = numHeader_mpiGlobal
      else
         obs_numHeader_mpiglobal = obsdat%numHeader
      end if
   end function obs_numHeader_mpiglobal



   subroutine obs_order(obsdat),5
      !
      ! PURPOSE:
      !      Put an obsdat file into the order required for the sequential
      !      assimilation. Note that it is known, as a by-product of the
      !      algorithm that was used  to determine the pass and the region for
      !      each station, at what exact location (information  in OBS_ONM) each
      !      station has to be. The algorithm requires the exchange of at most
      !      mxstn headers.  A faster algorithm likely exists.
      !
      ! author: Peter Houtekamer - March 2000
      !
      implicit none

      type (struct_obs), intent(inout) :: obsdat

      integer :: hdr,jk
      logical :: sorted
      integer :: bodyElem, first, last

      do hdr=1,obsdat%numHeader
         sorted=.false.
         do while(.not.sorted)
            jk=obs_headElem_i(obsdat, OBS_ONM, hdr)
            if (jk.eq.hdr) then
               sorted=.true.
            else
               call obs_exchange_stations(obsdat,jk,hdr)
            endif
         end do
      enddo

      do hdr=1,obsdat%numHeader
         ! Make the body members of header(hdr) point to the new row_index of hdr.
         first=        obs_headElem_i(obsdat, OBS_RLN, hdr)
         last =first + obs_headElem_i(obsdat, OBS_NLV, hdr) -1
         do bodyElem=first,last
            call obs_bodySet_i(obsdat, OBS_HIND, bodyElem, hdr)
         end do
      enddo

      return

   contains



      subroutine obs_exchange_stations(obsdat,j,k)  1,8
         !
         !author: Peter Houtekamer
         !        February 2000
         ! February 2011: Peter Houtekamer moved the original routine exchange
         !   from sortcma.f to the module.
         !
         !object: exchange the headers of stations j and k 
         !
         implicit none

         type (struct_obs), intent(inout) :: obsdat
         integer, intent(in)         :: j,k

         real(OBS_REAL):: rdum 
         integer       :: idum
         integer       :: column_index
         character(12) :: cdum

         do column_index=NHDR_INT_BEG, NHDR_INT_END
            if(obsdat%intHeaders%odc_flavour%columnActive(column_index)) then
               idum=obs_headElem_i(obsdat, column_index, j)
               call obs_headSet_i(obsdat, column_index, j, &
                   obs_headElem_i(obsdat, column_index, k))
               call obs_headSet_i(obsdat, column_index, k, idum)
            endif
         enddo

         do column_index=NHDR_REAL_BEG,NHDR_REAL_END
            if(obsdat%realHeaders%odc_flavour%columnActive(column_index)) then
               rdum=obs_headElem_r(obsdat, column_index, j)
               call obs_headSet_r(obsdat, column_index, j, &
                   obs_headElem_r(obsdat, column_index, k))
               call obs_headSet_r(obsdat, column_index, k, rdum)
            endif
         enddo

         cdum=obsdat%cstnid(j)
         obsdat%cstnid(j)=obsdat%cstnid(k)
         obsdat%cstnid(k)=cdum

         return
      end subroutine obs_exchange_stations
   end subroutine obs_order



   subroutine obs_print(obsdat,nobsout),2
      !
      ! object  - print the contents of the obsdat to an ASCII file
      !
      !author  : P. Houtekamer  February 2011
      !
      !arguments
      !     i   nobsout: unit used for printing
      !
      implicit none

      type(struct_obs), intent(inout) :: obsdat
      integer,         intent(in)    :: nobsout

      integer :: jo

      do jo=1,obsdat%numHeader
         call obs_enkf_prnthdr(obsdat,jo,nobsout)
         call obs_enkf_prntbdy(obsdat,jo,nobsout)
      enddo

      return
   end subroutine obs_print



   subroutine obs_prnt_csv(obsdat,nhdrsql,nbdysql),2
      !
      ! object  - print the contents of the obsdat to csv (comma separated
      !           values) files
      !
      !author  : P. Houtekamer  February 2011
      !
      !arguments
      !     i   nhdrsql: unit used for printing header
      !     i   nbdysql: unit used for printing body
      !
      implicit none

      type (struct_obs), intent(inout) :: obsdat
      integer,      intent(in)  :: nhdrsql, nbdysql

      integer :: jo

      do jo=1,obsdat%numHeader
         call obs_tosqlhdr(obsdat,jo,nhdrsql)
         call obs_tosqlbdy(obsdat,jo,nbdysql)
      enddo

      return
   end subroutine obs_prnt_csv



   subroutine obs_prntbdy(obsdat,index_header,unitout) 4,13
      !
      !**s/r PRNTBDY  - Print all data records associated with an observation
      !
      !Author  : P. Gauthier *ARMA/AES  June 9, 1992
      !Revision:
      !     . P. Gauthier *ARMA/AES May 20,1993: modifications to the CMA files
      !
      !     . C. Charette *ARMA/AES Mar 1996 : format statement
      !     . C. Charette *ARMA/AES Nov 1999 : Added print of flag OBS_ASS
      !       JM Belanger CMDA/SMC  Jul 2000
      !                   . 32 bits conversion
      !
      !Arguments
      !     i   index_header  : index of the group of observations to be printed
      !     i   unitout       : unit number on which to print
      !
      implicit none

      type(struct_obs), intent(in) :: obsdat
      integer         , intent(in) :: index_header
                                        ! variable output unit facilitates unit
                                        ! testing
      integer         , intent(in), optional :: unitout

      integer :: unitout_

      integer :: ipnt, idata, idata2, jdata, ivco
      character(len=13) :: ccordtyp(4)

      if(present(unitout)) then
         unitout_ = unitout
      else
         unitout_ = 6
      end if

      ccordtyp(1)='HEIGHT      :'
      ccordtyp(2)='PRESSURE    :'
      ccordtyp(3)='CHANNEL NUM :'
      ccordtyp(4)='VCO UNDEFINED'
      !
      ! 1. General information
      !
      ipnt  = obs_headElem_i(obsdat,OBS_RLN,index_header)
      idata = obs_headElem_i(obsdat,OBS_NLV,index_header)

      if(idata.eq.1) then
         write(unitout_,fmt=9101)idata,index_header, NBDY_INT_SIZE+NBDY_REAL_SIZE
      else
         write(unitout_,fmt=9100)idata,index_header, NBDY_INT_SIZE+NBDY_REAL_SIZE
      end if
9100  format(4x,'THERE ARE ', &
         i3,1x,'DATA IN OBSERVATION RECORD NO.' &
         ,1x,i6,4x,'DATA RECORD''S LENGTH:',i6)
9101  format(4x,'THERE IS ', &
         i3,1x,'DATUM IN OBSERVATION RECORD NO.' &
         ,1x,i6,4x,'DATA RECORD''S LENGTH:',i6)
      !
      ! 2. Print all data records
      !
      do jdata = ipnt, ipnt + idata - 1
         idata2 = jdata -ipnt + 1
         if(obs_bodyElem_i(obsdat,OBS_ASS,jdata).ge.0) then
            ivco=obs_bodyElem_i(obsdat,OBS_VCO,jdata)
            if(ivco.lt.1.or.ivco.gt.3) ivco=4
            write(unitout_,fmt=9201) idata2 &
               ,obs_bodyElem_i(obsdat,OBS_VNM ,jdata) &
               ,ccordtyp(ivco) &
               ,obs_bodyElem_r(obsdat,OBS_PPP ,jdata) &
               ,obs_bodyElem_r(obsdat,OBS_VAR ,jdata) &
               ,obs_bodyElem_r(obsdat,OBS_OMP ,jdata) &
               ,obs_bodyElem_r(obsdat,OBS_OMA ,jdata) &
               ,obs_bodyElem_r(obsdat,OBS_OER ,jdata) &
               ,obs_bodyElem_r(obsdat,OBS_HPHT,jdata) &
               ,obs_bodyElem_i(obsdat,OBS_FLG ,jdata) &
               ,obs_bodyElem_i(obsdat,OBS_ASS ,jdata)
         end if
      end do

9201  format(4x,'DATA NO.',i6,/,10x &
         ,'VARIABLE NO.:',i6,4x,a13,g12.6,4x &
         ,/,10x &
         ,'OBSERVED VALUE:',g23.16,5x,'OBSERVED - BACKGROUND VALUES:' &
         ,g23.16,4x &
         ,/,10x &
         ,'OBSERVED - ANALYZED VALUES:',g12.6,4x &
         ,/,10x &
         ,'ERROR STANDARD DEVIATIONS FOR' &
         ,/,20x &
         ,'OBSERVATION:',g12.6,4x &
         ,/,20x &
         ,'FIRST-GUESS:',g12.6,4x &
         ,/,10x &
         ,'BURP FLAGS:',i6,4x,'OBS. ASSIMILATED (1-->YES;0-->NO):',i3)

      return
   end subroutine obs_prntbdy



   subroutine obs_prnthdr(obsdat,index_hd,unitout) 4,14
      !
      !**s/r PRNTHDR  - Printing of the header of an observation record
      !
      !Author  : P. Gauthier *ARMA/AES  June 9, 1992
      !Revision:
      !     . P. Gauthier *ARMA/AES May 20,1993: modifications to the CMA files
      !     . P. Koclas   *CMC: Format for transformed latitude has been modified
      !     .                   to handle an integer (latitude index of the first
      !     .                   latitude circle north of the observation)
      !Arguments
      !     i   index_hd  : index of the header to be printed
      !     i   unitout       : unit number on which to print
      !

      implicit none


      type(struct_obs), intent(in) :: obsdat
      integer         , intent(in) :: index_hd
                                        ! variable output unit facilitates unit
                                        ! testing
      integer         , intent(in), optional :: unitout

      integer :: unitout_

      if(present(unitout)) then
         unitout_ = unitout
      else
         unitout_ = 6
      end if

      !
      ! 1. General information
      !
      write(unitout_,fmt=9100)index_hd, NHDR_INT_SIZE + NHDR_REAL_SIZE
9100  format(//,10x,'-- OBSERVATION RECORD NO.' &
         ,1x,i6,3x,'HEADER''S LENGTH:',i6)
      !
      ! 2. PRINT HEADER'S CONTENT
      !
      write(unitout_,fmt=9200)&
          obs_headElem_i(obsdat,OBS_RLN,index_hd) &
         ,obs_headElem_i(obsdat,OBS_ONM,index_hd) &
         ,obs_headElem_i(obsdat,OBS_INS,index_hd) &
         ,obs_headElem_i(obsdat,OBS_OTP,index_hd) &
         ,obs_headElem_i(obsdat,OBS_ITY,index_hd) &
         ,obs_headElem_r(obsdat,OBS_LAT,index_hd) &
         ,obs_headElem_r(obsdat,OBS_LON,index_hd) &
         ,obs_headElem_i(obsdat,OBS_DAT,index_hd) &
         ,obs_headElem_i(obsdat,OBS_ETM,index_hd) &
         ,obs_elem_c(obsdat,'STID',index_hd)      &
         ,obs_headElem_r(obsdat,OBS_ALT,index_hd) &
         ,obs_headElem_i(obsdat,OBS_NLV,index_hd) &
         ,obs_headElem_i(obsdat,OBS_OFL,index_hd) &
         ,obs_headElem_i(obsdat,OBS_ST1,index_hd)

9200  format(6x,'Position within realBodies:',i6,1x,'OBS. NUMBER:',i6,1x &
         ,'INSTR. ID:',i6,1x,'OBS. TYPE:',i6,1x &
         ,'INSTR./RETR. TYPE:',i6,1x &
         ,/,6x &
         ,'OBSERVATION LOCATION. (LAT,LON):',2(f10.4,1x) &
         ,'DATE:',i12,1x,'EXACT TIME: ',i6,1x &
         ,/,6x &
         ,'STATION ID:',a9,1x &
         ,'STATION''S ALTITUDE:',g12.6,1x &
         ,'NUMBER OF DATA:',i6,1x &
         ,/,6x &
         ,'REPORT STATUS:',i6,5x,'REPORT STATUS 2:',i6,1x &
         ,/,6x &
         )

      return
   end subroutine obs_prnthdr



   subroutine obs_read(obsdat,hx,nobshdr,nobsbdy,nobshx),18
      !
      !authors Peter Houtekamer and Herschel Mitchell October 1999
      !
      !object: read the obsdat structure with observational information from
      !        unformatted files.  The files have been written by obs_write().
      !
      !   input: 
      !      nobshdr: unit number of the file with obsdat header info.
      !      nobsbdy: unit number of the file with obsdat body info.
      !      nobshx:  unit number of the file with hx (-1 if not used)
      !   output:
      !      intHeaders,realHeaders,cstnid:         station header information
      !      intBodies,realBodies,hx:               observation data
      !
      !NOTE:  It is assumed that the obsdat arrays have already been allocated
      !       with dimensions that will exactly hold the number of data to be
      !       read
      !
      implicit none

      type (struct_obs), intent(inout) :: obsdat  ! the OBSDAT being prep'ed
      integer,      intent(in)  :: nobshdr,nobsbdy,nobshx
      real(8),      intent(out) :: hx(:,:)

      integer  :: i,ifirst,ilast,iobscur,istn,j,k,myip,nens
      integer  :: column_index
      integer  :: active_index
      character(len=100) :: message

      obsdat%mpi_local = .false.

      if (nobshx.eq.-1) then
         nens=0
      else
         nens=size(hx,1)
      endif
                                        ! get index of 1st active RB column
      column_index=odc_columnIndexFromActiveIndex( &
                                                 obsdat%realBodies%odc_flavour,1)
      obsdat%numBody=size(obsdat%realBodies%columns(column_index)%value_r,1)
                                        ! get index of 1st active RH column
      column_index=odc_columnIndexFromActiveIndex( &
                                                obsdat%realHeaders%odc_flavour,1)
      obsdat%numHeader=size(obsdat%realHeaders%columns(column_index)%value_r,1)

      iobscur=0

      ! read stations

      readstn: do istn=1,obsdat%numHeader
         read(nobshdr,end=288,err=288) &
            (obsdat%intHeaders%columns(odc_columnIndexFromActiveIndex( &
                                               obsdat%intHeaders%odc_flavour,i) &
                                      )%value_i(istn),&
                 i=1,odc_numActiveColumn(obsdat%intHeaders)),&
            (obsdat%realHeaders%columns(odc_columnIndexFromActiveIndex( &
                                              obsdat%realHeaders%odc_flavour,j) &
                                       )%value_r(istn),&
                 j=1,odc_numActiveColumn(obsdat%realHeaders)),&
            obsdat%cstnid(istn), &
            obsdat%cfamily(istn)

         if (istn.eq.1) then 
            call obs_headSet_i(obsdat, OBS_RLN, istn, 1)
         else
            call obs_headSet_i (obsdat, OBS_RLN, istn, &
                 obs_headElem_i(obsdat, OBS_RLN, istn-1) &
                +obs_headElem_i(obsdat, OBS_NLV, istn-1))
         endif
         iobscur=iobscur+obs_headElem_i(obsdat, OBS_NLV, istn)
         ! now read the observations:
         ifirst=         obs_headElem_i(obsdat, OBS_RLN, istn)
         ilast =ifirst + obs_headElem_i(obsdat, OBS_NLV, istn) -1
         do i=ifirst,ilast
            read(nobsbdy) &
              (obsdat%intBodies%columns(odc_columnIndexFromActiveIndex( &
                                                obsdat%intBodies%odc_flavour,j) &
                                       )%value_i(i),&
                   j=1,odc_numActiveColumn(obsdat%intBodies)),&
              (obsdat%realBodies%columns(odc_columnIndexFromActiveIndex( &
                                               obsdat%realBodies%odc_flavour,k) &
                                        )%value_r(i),&
                   k=1,odc_numActiveColumn(obsdat%realBodies))
         enddo
         if (nens.gt.0) then
            do i=ifirst,ilast
               read(nobshx)  (hx(j,i),j=1,nens)
            enddo
         endif
      enddo readstn

      if (iobscur.ne.obsdat%numBody) then
         write(message,*)'OBS_READ: the number of references in the header, ', &
                         iobscur, ', does not match the body size, ', &
                         obsdat%numBody
         call obs_abort(message); return
      endif
288   write(*,*) 'file is now empty'
      write(*,*) 'close nobshdr which is on unit: ',nobshdr
      close(nobshdr)
      write(*,*) 'close nobsbdy which is on unit: ',nobsbdy
      close(nobsbdy)
      if (nens.gt.0) then
         write(*,*) 'close nobshx which is on unit: ',nobshx
         close(nobshx)
      endif
      write(*,*) 'exit from obs_read'
      return

   end subroutine obs_read



   subroutine obs_readstns(obsdat,myip,ipasscur,iregcur,nobshdr,nobsbdy,np, &,23
                           mxstn,mxobs)
      !
      ! obs_readstns
      !
      !authors Peter Houtekamer and Herschel Mitchell October 1999
      !
      !object: read the stations for one analysis pass, from unformatted files,
      !        and store them in an ObsSpaceData_mod object.  The files have been
      !        written by obs_write().
      !        (this routine is intended for the master mpi process,
      !         other processes exit immediately)
      !
      !   input: 
      !      myip:     number of the process
      !      ipasscur: number of the current analysis pass (i.e. batch)
      !      nobshdr:  unit number of the file with obsdat header info.
      !      nobsbdy:  unit number of the file with obsdat body info.
      !      np   :    total number of processes used in MPI. 
      !   output:
      !      iregcur: number of the region to be done for this pass. 
      !
      implicit none

      type(struct_obs), intent(inout) :: obsdat
      integer,          intent(in)  :: ipasscur,myip,nobshdr,nobsbdy,np,mxstn, &
                                       mxobs
      integer,          intent(out) :: iregcur

      integer :: i,idata,ifirst,ilast,ipass,ireg,j,k
      integer :: active_index
      integer :: column_index

      real(OBS_REAL),    save :: realHeaders_1(1:NHDR_REAL_SIZE)
      integer,           save :: intHeaders_1(1:NHDR_INT_SIZE)
      character(len=12), save :: cstnid_1
      character(len=2),  save :: cfamily_1
      logical,           save :: empty  = .false., &
                                 hasone = .false.

      if (myip.ne.0) return

      if (empty) then
         write(*,*) 'file is empty'
         return
      endif

      iregcur=1

      ! read headers for this pass of the sequential algorithm

      ! hasone indicates whether a header has been read without being
      ! inserted into obsdat. This occurs after the headers for one pass have
      ! been read. In this case one should not read a new header 
      ! but first insert the saved one.

      do while( get_one() )
         ireg =intHeaders_1(odc_activeIndexFromColumnIndex( &
                                          obsdat%intHeaders%odc_flavour,OBS_REG))
         ipass=intHeaders_1(odc_activeIndexFromColumnIndex( &
                                          obsdat%intHeaders%odc_flavour,OBS_PAS))

         if ((ipass /= ipasscur)) then
            if(obsdat%numHeader == 0) then
               write(6,*)"ERROR"
               write(6,*)"ERROR:  In obs_readstns(), the next"
               write(6,*)"ERROR:  OBS_PAS value, ", ipass, "does not match the"
               write(6,*)"ERROR:  current  pass, ", ipasscur,".  Exiting."
               write(6,*)"ERROR"
               call obs_abort('OBS_READSTNS: ipass /= ipasscur')
               return
            end if
            exit
         end if

         ! default assignment of all input variables to obsdat.
         obsdat%numHeader=obsdat%numHeader+1
         idata=obsdat%numHeader
         do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
            column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%intHeaders%odc_flavour, active_index)
            if(obsdat%intHeaders%odc_flavour%columnActive(column_index))  &
               call obs_headSet_i(obsdat, column_index, idata, &
                                  intHeaders_1(active_index))
                                                         
         enddo
         do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
            column_index=odc_columnIndexFromActiveIndex( &
                                    obsdat%realHeaders%odc_flavour, active_index)
            if(obsdat%realHeaders%odc_flavour%columnActive(column_index))  &
               call obs_headSet_r(obsdat, column_index, idata, &
                                  realHeaders_1(active_index))
                                                         
         enddo
         obsdat%cstnid(idata)  =cstnid_1
         obsdat%cfamily(idata) =cfamily_1

         ! determine which process will handle this station.
         ! the corresponding scatter operation is in program scattercma.
         call obs_headSet_i(obsdat,OBS_IP,idata,mod(ipasscur,np))

         ireg=obs_headElem_i(obsdat, OBS_REG, idata)
         if (iregcur.ne.ireg) then
            iregcur=ireg
         endif

         if (idata.eq.1) then
            call obs_headSet_i(obsdat, OBS_RLN, idata, 1)
         else
            call obs_headSet_i(obsdat, OBS_RLN, idata, &
                obs_headElem_i(obsdat, OBS_RLN, idata-1) &
               +obs_headElem_i(obsdat, OBS_NLV, idata-1))
         endif

         ! now read the bodies:
         ifirst=         obs_headElem_i(obsdat, OBS_RLN, idata)
         ilast =ifirst + obs_headElem_i(obsdat, OBS_NLV, idata)-1
         do i=ifirst,ilast
            read(nobsbdy) &
              (obsdat%intBodies%columns(odc_columnIndexFromActiveIndex( &
                                                obsdat%intBodies%odc_flavour,j) &
                                       )%value_i(i), &
                   j=1,odc_numActiveColumn(obsdat%intBodies)), &
              (obsdat%realBodies%columns(odc_columnIndexFromActiveIndex( &
                                               obsdat%realBodies%odc_flavour,k) &
                                        )%value_r(i), &
                   k=1,odc_numActiveColumn(obsdat%realBodies))
                                        ! Make HIND point to new header row_index
            call obs_bodySet_i(obsdat, OBS_HIND, i, idata)
         enddo

         hasone=.false.

         ! go back to read the next station
      end do

      return


   contains

      logical function get_one() 1,2
         integer :: ierr

         if (.not. hasone) then 
            read(nobshdr,iostat=ierr) &
                 (intHeaders_1(i),i=1,odc_numActiveColumn(obsdat%intHeaders)),&
                 (realHeaders_1(j),j=1,odc_numActiveColumn(obsdat%realHeaders)),&
                 cstnid_1, &
                 cfamily_1
            if(ierr == 0) then
               hasone = .true.
            else
               hasone = .false.
               empty=.true.
               write(*,*) 'file is now empty'
               close(nobshdr)
               close(nobsbdy)
            end if ! ierr
         end if ! hasone

         get_one = hasone
         return
      end function get_one

   end subroutine obs_readstns



   subroutine obs_reduceToMpiLocal(obsdat) 1,39
      !
      !**s/r obs_reduceToMpiLocal - re-construct observation data object by
      !                             giving local Obs TAG. 
      !
      ! PURPOSE:
      !      To retain in the observation object only those data that are
      !      pertinent to the present mpi processor, i.e. convert from mpiglobal
      !      to mpilocal.
      !
      ! author  : Bin He (ARMA/MRB )
      ! revision:
      !
      implicit none

      type(struct_obs), intent(inout) :: obsdat

      ! Declare Local Variables
      character(len=12),allocatable,dimension(:)   :: cstnid_tmp
      character(len=2), allocatable,dimension(:)   :: cfamily_tmp
      real(OBS_REAL),   allocatable,dimension(:,:) :: realHeaders_tmp
      real(OBS_REAL),   allocatable,dimension(:,:) :: realBodies_tmp

      integer,allocatable,dimension(:,:) :: intHeaders_tmp,intBodies_tmp

      integer :: i,j,startindx,endindx 
      integer :: numHeader_mpilocal,numHeader_mpiglobal
      integer ::   numBody_mpilocal,  numBody_mpiglobal
      integer :: bodyIndex_mpilocal,bodyIndex_mpiglobal
      integer :: headerIndex_mpilocal,headerIndex_mpiglobal
      integer :: idataend,jj,ifamid,istart,idata,active_index
      integer :: column_index
      logical :: lfirst 
      !!---------------------------------------------------------------
      WRITE(*,*) '============= Enter obs_reduceToMpiLocal =============='

      if(obsdat%mpi_local)then
         call obs_abort('OBS_REDUCETOMPILOCAL() has been called, but the ' &
                        // 'obsSpaceData object is already in mpi-local state')
         return
      end if

      if(obsdat%numHeader_max.eq.0)then
         call obs_abort('OBS_REDUCETOMPILOCAL() has been called when there are '&
                        // 'no data.  Obs_reduceToMpiLocal cannot be called ' &
                        // 'after a call to obs_expandToMpiGlobal.')
         return
      end if

      ! compute the mpilocal lists of indices into the mpiglobal data
      call obs_mpiDistributeIndices(obsdat)

      ! calculate the size of the local obs data  
      if(associated(obsdat%headerIndex_mpiglobal)) then
         numHeader_mpilocal=size(obsdat%headerIndex_mpiglobal) 
      else
         numHeader_mpilocal=0
      endif
      numBody_mpiLocal=0
      do headerIndex_mpilocal=1,numHeader_mpilocal
         headerIndex_mpiglobal=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
         idata=obs_headElem_i(obsdat, OBS_NLV, headerIndex_mpiglobal)
         numBody_mpiLocal = numBody_mpiLocal + idata
      enddo

      numHeader_mpiGlobal = obs_numHeader(obsdat)
      numBody_mpiGlobal   = obs_numBody(obsdat)

      ! allocate temporary arrays to hold mpilocal data
      if(numHeader_mpiLocal.gt.0) then
         allocate(cfamily_tmp(    numHeader_mpiLocal)) 
         allocate( cstnid_tmp(    numHeader_mpiLocal)) 
         allocate(realHeaders_tmp(odc_numActiveColumn(obsdat%realHeaders), &
                                  numHeader_mpilocal))
         allocate( intHeaders_tmp(odc_numActiveColumn(obsdat%intHeaders), &
                                  numHeader_mpilocal))
      endif

      if(numBody_mpiLocal.gt.0) then
         allocate( realBodies_tmp(odc_numActiveColumn(obsdat%realBodies), &
                                  numBody_mpilocal))
         allocate(  intBodies_tmp(odc_numActiveColumn(obsdat%intBodies), &
                                  numBody_mpilocal))
      endif

      ! copy the mpilocal data to temporary arrays: header-level data
      do headerIndex_mpilocal=1,numHeader_mpilocal 
         headerIndex_mpiglobal=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)

         do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
            column_index=odc_columnIndexFromActiveIndex( &
                                    obsdat%realHeaders%odc_flavour, active_index)
            realHeaders_tmp(active_index,headerIndex_mpilocal)= &
                      obs_headElem_r(obsdat, column_index, headerIndex_mpiglobal)
         enddo

         do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
            column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%intHeaders%odc_flavour, active_index)
            intHeaders_tmp(active_index,headerIndex_mpilocal)= &
                      obs_headElem_i(obsdat, column_index, headerIndex_mpiglobal)
         enddo

         cstnid_tmp (headerIndex_mpilocal) =obsdat%cstnid (headerIndex_mpiglobal)
         cfamily_tmp(headerIndex_mpilocal) =obsdat%cfamily(headerIndex_mpiglobal)

                                        ! Make RLN point to local data
         if(headerIndex_mpilocal == 1) then
            intHeaders_tmp &
                (odc_activeIndexFromColumnIndex( &
                                        obsdat%intHeaders%odc_flavour,OBS_RLN), &
                 1 &
                ) = 1
         else
            intHeaders_tmp &
                (odc_activeIndexFromColumnIndex( &
                                        obsdat%intHeaders%odc_flavour,OBS_RLN), &
                 headerIndex_mpilocal &
                ) = intHeaders_tmp(odc_activeIndexFromColumnIndex( &
                                        obsdat%intHeaders%odc_flavour,OBS_RLN), &
                                   headerIndex_mpilocal-1 &
                                  ) &
                  + intHeaders_tmp(odc_activeIndexFromColumnIndex( &
                                        obsdat%intHeaders%odc_flavour,OBS_NLV), &
                                   headerIndex_mpilocal-1 &
                                  ) 
         endif
      enddo

      ! copy the mpilocal data to temporary arrays: body-level data
      bodyIndex_mpilocal=0 
      do headerIndex_mpilocal=1,numHeader_mpilocal
         headerIndex_mpiglobal=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
 
                                        ! Make HIND point to local header
         idata    = obs_headElem_i(obsdat, OBS_RLN,headerIndex_mpiglobal)
         idataend = obs_headElem_i(obsdat, OBS_NLV,headerIndex_mpiglobal)+idata-1
         do bodyIndex_mpiglobal=idata,idataend 
            bodyIndex_mpilocal=bodyIndex_mpilocal+1 
            do active_index=1,odc_numActiveColumn(obsdat%realBodies)
               column_index=odc_columnIndexFromActiveIndex( &
                                      obsdat%realBodies%odc_flavour,active_index)
               realBodies_tmp(active_index,bodyIndex_mpilocal)= &
                        obs_bodyElem_r(obsdat, column_index, bodyIndex_mpiglobal)
            enddo

            do active_index=1,odc_numActiveColumn(obsdat%intBodies)
               column_index=odc_columnIndexFromActiveIndex( &
                                       obsdat%intBodies%odc_flavour,active_index)
               intBodies_tmp(active_index,bodyIndex_mpilocal)= &
                        obs_bodyElem_i(obsdat, column_index, bodyIndex_mpiglobal)
            enddo

            intBodies_tmp(odc_activeIndexFromColumnIndex( &
                                       obsdat%intBodies%odc_flavour, OBS_HIND), &
                          bodyIndex_mpilocal &
                         ) = headerIndex_mpilocal
         enddo
      enddo

      ! destroy object's mpiglobal data and allocate mpilocal data
      obsdat%numHeader=numHeader_mpiLocal
      obsdat%numBody=numBody_mpiLocal
      call obs_deallocate(obsdat)
      call obs_allocate(obsdat,obsdat%numHeader,obsdat%numBody)

      ! copy all data from temporary arrays to object's arrays
      HEADER:if(numHeader_mpiLocal.gt.0) then
         obsdat%cfamily(:  )=cfamily_tmp(:  ) 
         obsdat%cstnid (:  )= cstnid_tmp(:  )
         do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
            column_index=odc_columnIndexFromActiveIndex( &
                                    obsdat%realHeaders%odc_flavour, active_index)
            obsdat%realHeaders%columns(column_index)%value_r(:) &
                                                 =realHeaders_tmp(active_index,:)
         enddo
         do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
            column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%intHeaders%odc_flavour, active_index)
            obsdat%intHeaders%columns(column_index)%value_i(:) &
                                                  =intHeaders_tmp(active_index,:)
         enddo

         ! deallocate temporary arrays
         deallocate(cfamily_tmp)
         deallocate(cstnid_tmp)
         deallocate(realHeaders_tmp)
         deallocate(intHeaders_tmp)
      endif HEADER

      BODY:if(numBody_mpiLocal.gt.0) then
         do active_index=1,odc_numActiveColumn(obsdat%realBodies)
            column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%realBodies%odc_flavour, active_index)
            obsdat%realBodies%columns(column_index)%value_r(:) &
                                                  =realBodies_tmp(active_index,:)
         enddo
         do active_index=1,odc_numActiveColumn(obsdat%intBodies)
            column_index=odc_columnIndexFromActiveIndex( &
                                      obsdat%intBodies%odc_flavour, active_index)
            obsdat%intBodies%columns(column_index)%value_i(:) &
                                                  =intBodies_tmp(active_index,:) 
         enddo

         ! deallocate temporary arrays
         deallocate(realBodies_tmp)
         deallocate(intBodies_tmp)
      endif BODY


      obsdat%mpi_local = .true.

      write(*,*) '============= Leave obs_reduceToMpiLocal =============='

      return
   end subroutine obs_reduceToMpiLocal



   subroutine obs_select(obsdat,hx,obs_sel,hx_sel,zhamin,zhamax,nens,nobsout),23
      !
      ! object  - select only the observations with zhamin < lop(P) <= zhamax.   
      !
      !author  : Peter Houtekamer
      !     January 2012: created using obs_clean as an example
      !
      !arguments
      !     obsdat,hx        : input obsdat and interpolated values
      !     obs_sel,hx_sel: selected obsdat and interpolated values
      !     zhamin,zhamax : range of zha values to be selected.
      !     nens          : number of ensemble members
      !     nobsout       : unit number for the ASCII output
      !
      implicit none

      type (struct_obs), intent(in) :: obsdat
      type (struct_obs), intent(inout) :: obs_sel

      real(8),        intent(in) :: hx(:,:)
      real(OBS_REAL), intent(in) :: zhamin,zhamax
      real(8),        intent(out):: hx_sel(:,:)

      integer, intent(in)    :: nens, nobsout

      integer :: iaccept,idata,ipnt,iwrite
      integer :: jdata,kobs,kobsout
      integer :: column_index
      integer :: active_index

      if(obsdat%mpi_local)then
         call obs_abort('obs_select() is not equipped to handle the case, ' // &
                        'mpi_local=.true.')
         return
      end if

      write(nobsout,'(1x,A,I7)')'stations prior to selection: ', obsdat%numHeader
      write(*,*) 'enter obs_select'

      kobsout=0 
      iwrite=0
      stations: do kobs=1,obsdat%numHeader
         ipnt  = obs_headElem_i(obsdat, OBS_RLN, kobs)
         idata = obs_headElem_i(obsdat, OBS_NLV, kobs)
         iaccept=0
         observations: do jdata = ipnt, ipnt + idata - 1
            ! To remove observations that are not in the desired vertical layer 
            if ((obs_bodyElem_r(obsdat, OBS_ZHA, jdata) .gt. zhamin).and. &
                (obs_bodyElem_r(obsdat, OBS_ZHA, jdata) .le. zhamax)) then
               iaccept=iaccept+1
               iwrite=iwrite+1
               do active_index=1,odc_numActiveColumn(obsdat%intBodies)
                  column_index=odc_columnIndexFromActiveIndex( &
                                      obsdat%intBodies%odc_flavour, active_index)
                  call obs_bodySet_i(obs_sel, column_index, iwrite, &
                       obs_bodyElem_i(obsdat, column_index, jdata))
               enddo
               do active_index=1,odc_numActiveColumn(obsdat%realBodies)
                  column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%realBodies%odc_flavour, active_index)
                  call obs_bodySet_r(obs_sel, column_index, iwrite, &
                       obs_bodyElem_r(obsdat, column_index, jdata))
               enddo
               hx_sel(1:nens,iwrite)=hx(1:nens,jdata)
            endif
         enddo observations

         ! adjust obs_sel%*Headers%columns
         if (iaccept.gt.0) then
            kobsout=kobsout+1
            do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
               column_index=odc_columnIndexFromActiveIndex( &
                                     obsdat%intHeaders%odc_flavour, active_index)
               call obs_headSet_i(obs_sel, column_index, kobsout, &
                    obs_headElem_i(obsdat, column_index, kobs))
            enddo
            do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
               column_index=odc_columnIndexFromActiveIndex( &
                                    obsdat%realHeaders%odc_flavour, active_index)
               call obs_headSet_r(obs_sel, column_index, kobsout, &
                    obs_headElem_r(obsdat, column_index, kobs))
            enddo
            obs_sel%cstnid(kobsout)=obsdat%cstnid(kobs)
            obs_sel%cfamily(kobsout)=obsdat%cfamily(kobs)
            call obs_headSet_i(obs_sel, OBS_NLV, kobsout, iaccept)
            call obs_headSet_i(obs_sel, OBS_RLN, kobsout, iwrite-iaccept+1)
         endif
      enddo stations
      obs_sel%mpi_local = .false.

      write(nobsout, '(1x, A, 2F11.6)') &
                 'after selection of observations in the range: ', zhamin, zhamax
      write(nobsout,'(1x,A,I7)') &
         'number of stations containing valid data      ',obs_sel%numHeader
      write(nobsout,'(1x,A,I7)') & 
         'number of observations now in the obsdat file ',obs_sel%numBody

   end subroutine obs_select



   subroutine obs_set_c(obsdat, name, row_index, value) 1,1
      !s/r obs_set_c - set a character(len=9) in the observation object
      !
      ! PURPOSE:
      !      To control access to the observation object.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs), intent(inout)  :: obsdat
      character(len=*), intent(in)     :: name
      integer         , intent(in)     :: row_index
      character(len=*), intent(in)     :: value

      select case (trim(name))
      case ('STID'); obsdat%cstnid (row_index) = value
         if(row_index == (obsdat%numHeader+1)) obsdat%numHeader = obsdat%numHeader+1

      case default
         call obs_abort('ERROR writing:  ' // trim(name) // &
                        ' is not a character(len=9) observation.')
         return
      end select
   end subroutine obs_set_c



   subroutine obs_set_current_body_list_from_family(obsdat, family, & 1,6
      list_is_empty, current_list)
      !
      ! PURPOSE:
      !      Create a row_index list from the indicated family and place it in
      !      the body depot.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs), intent(inout), target :: obsdat
      character(len=*), intent(in) :: family
      logical, intent(out), optional :: list_is_empty
      type(struct_index_list), pointer, intent(out), optional :: current_list

      type(struct_index_list_depot), pointer :: depot
      type(struct_index_list), pointer :: index_list
      integer :: index_header, list, list_index, row_index
      integer :: first, last

      nullify(index_list)
      depot => obsdat%body_index_list_depot

      ! Search for an existing list
      if(present(current_list)) then
         if(associated(current_list)) then
            if (current_list%family == family) then
               index_list => current_list
            end if ! family matches
         end if ! associated

      else ! not present(current_list)
         do list = 1, NUMBER_OF_LISTS
            if (depot%index_lists(list)%family == family) then
               index_list => depot%index_lists(list)
               exit                     ! Don't look any further
            end if
         end do
      end if

      ! If the list does not already exist
      if (.not. associated(index_list)) then

         ! Acquire memory for the list
         if(present(current_list)) then
            ! This is an OMP thread. Re-use the same physical memory for the list
            index_list => ild_get_empty_index_list(depot, current_list)
         else
            index_list => ild_get_empty_index_list(depot)
         end if

         ! Initialize the new list
         index_list%family = family
         index_list%header = -99        ! not used

         !
         ! Populate the list
         !

         ! Loop over all header indices of the family
         list_index = 0
         call obs_set_current_header_list(obsdat, family)
         HEADER: do
            index_header = obs_getHeaderIndex(obsdat)
            if (index_header < 0) exit HEADER
            first= obs_headElem_i(obsdat,OBS_RLN,index_header)
            last = obs_headElem_i(obsdat,OBS_NLV,index_header) + first - 1
            do row_index=first,last     ! For each item indicated in the header
               ! Add the row_index to the list
               list_index = list_index + 1
               index_list%indices(list_index) = row_index
            end do
         end do HEADER
         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
         index_list%indices(list_index+2)= -1 ! ... clearly
      end if ! list does not already exist

      index_list%current_element = 0    ! Set pointer to the start of the list
      depot%current_list => index_list  ! Note the current list

      if(present(list_is_empty)) then
         ! Return whether the list is empty
         list_is_empty = (ild_get_next_index(depot, no_advance=.true.) < 0)
      end if

      if(present(current_list)) then
         ! Return a pointer to the current list
         current_list => index_list
      end if
   end subroutine obs_set_current_body_list_from_family



   subroutine obs_set_current_body_list_from_header(obsdat, header, & 1,4
      list_is_empty, current_list)
      !
      ! PURPOSE:
      !      Create a row_index list from the indicated header and place it in
      !      the body depot.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs), intent(inout), target :: obsdat
      integer, intent(in) :: header
      logical, intent(out), optional :: list_is_empty
      type(struct_index_list), pointer, intent(out), optional :: current_list

      type(struct_index_list_depot), pointer :: depot
      type(struct_index_list), pointer :: index_list
      integer :: list, list_index, row_index
      integer :: first, last

      nullify(index_list)
      depot => obsdat%body_index_list_depot

      ! Search for an existing list
      if(present(current_list)) then
         if(associated(current_list)) then
            if (current_list%header == header) then
               index_list => current_list
            end if ! header matches
         end if ! associated

      else ! not present(current_list)
         do list = 1, NUMBER_OF_LISTS
            if (depot%index_lists(list)%header == header) then
               index_list => depot%index_lists(list)
               exit                     ! Don't look any further
            end if
         end do
      end if

      ! If the list does not already exist
      if (.not. associated(index_list)) then

         ! Acquire memory for the list
         if(present(current_list)) then
            ! This is an OMP thread. Re-use the same physical memory for the list
            index_list => ild_get_empty_index_list(depot, current_list)
         else
            index_list => ild_get_empty_index_list(depot)
         end if

         ! Initialize the new list
         index_list%family = 'xx'       ! not used
         index_list%header = header

         ! Populate the list
         first= obs_headElem_i(obsdat,OBS_RLN,header)
         last = obs_headElem_i(obsdat,OBS_NLV,header) + first - 1
         list_index = 0
         do row_index=first,last        ! For each item indicated in the header
            ! Add the row_index to the list
            list_index = list_index + 1
            index_list%indices(list_index) = row_index
         end do
         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
         index_list%indices(list_index+2)= -1 ! ... clearly
      end if ! list does not already exist

      index_list%current_element = 0    ! Set pointer to the start of the list
      depot%current_list => index_list  ! Note the current list

      if(present(list_is_empty)) then
         ! Return whether the list is empty
         list_is_empty = (ild_get_next_index(depot, no_advance=.true.) < 0)
      end if

      if(present(current_list)) then
         ! Return a pointer to the current list
         current_list => index_list
      end if
   end subroutine obs_set_current_body_list_from_header



   subroutine obs_set_current_body_list_all(obsdat, list_is_empty, current_list) 1,6
      !
      ! PURPOSE:
      !      Create a row_index list containing all bodies and place it in the
      !      body depot.
      !
      ! author  : J.W. Blezius - 2014
      !
      implicit none
      type(struct_obs), intent(inout), target :: obsdat
      logical, intent(out), optional :: list_is_empty
      type(struct_index_list), pointer, intent(out), optional :: current_list

      type(struct_index_list_depot), pointer :: depot
      type(struct_index_list), pointer :: index_list
      integer :: list, list_index, row_index, index_header
      integer :: first, last

      nullify(index_list)
      depot => obsdat%body_index_list_depot

      ! Search for an existing list
      if(present(current_list)) then
         if(associated(current_list)) then
            if (      current_list%header == -1 &
                .and. current_list%family == '  ') then
               index_list => current_list
            end if ! null header and family
         end if ! associated

      else ! not present(current_list)
         do list = 1, NUMBER_OF_LISTS
            if (      depot%index_lists(list)%header == -1 &
                .and. depot%index_lists(list)%family == '  ') then
               index_list => depot%index_lists(list)
               exit                     ! Don't look any further
            end if
         end do
      end if

      ! If the list does not already exist
      if (.not. associated(index_list)) then

         ! Acquire memory for the list
         if(present(current_list)) then
            ! This is an OMP thread. Re-use the same physical memory for the list
            index_list => ild_get_empty_index_list(depot, current_list)
         else
            index_list => ild_get_empty_index_list(depot)
         end if

         ! Initialize the new list
         index_list%family = '  '       ! null
         index_list%header = -1         ! null

         !
         ! Populate the list
         !

         ! Loop over all header indices
         list_index = 0
         call obs_set_current_header_list(obsdat)
         HEADER: do
            index_header = obs_getHeaderIndex(obsdat)
            if (index_header < 0) exit HEADER
            first= obs_headElem_i(obsdat,OBS_RLN,index_header)
            last = obs_headElem_i(obsdat,OBS_NLV,index_header) + first - 1
            do row_index=first,last     ! For each item indicated in the header
               ! Add the row_index to the list
               list_index = list_index + 1
               index_list%indices(list_index) = row_index
            end do
         end do HEADER
         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
         index_list%indices(list_index+2)= -1 ! ... clearly
      end if ! list does not already exist

      index_list%current_element = 0    ! Set pointer to the start of the list
      depot%current_list => index_list  ! Note the current list

      if(present(list_is_empty)) then
         ! Return whether the list is empty
         list_is_empty = (ild_get_next_index(depot, no_advance=.true.) < 0)
      end if

      if(present(current_list)) then
         ! Return a pointer to the current list
         current_list => index_list
      end if
   end subroutine obs_set_current_body_list_all



   subroutine obs_set_current_header_list_from_family(obsdat, family) 1,1
      !
      ! PURPOSE:
      !      Find or create a row_index list for the indicated family and place
      !      it in the header depot.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs), intent(inout), target :: obsdat
      character(len=*), intent(in) :: family

      type(struct_index_list_depot), pointer :: depot
      type(struct_index_list), pointer :: index_list
      integer :: list, list_index, row_index

      nullify(index_list)
      depot => obsdat%header_index_list_depot

      ! Search for an existing list
      do list = 1, NUMBER_OF_LISTS
         if (depot%index_lists(list)%family == family) then
            index_list => depot%index_lists(list)
            index_list%current_element=0! Start at the beginning of the list
            exit                        ! Don't look any further
         end if
      end do

      ! If the list does not already exist
      if (.not. associated(index_list)) then
         ! Create a new list
         index_list => ild_get_empty_index_list(depot)
         index_list%family = family
         index_list%header = -1

         ! Populate the list
         list_index = 0
         do row_index = 1, obsdat%numHeader
            ! If the station is of the right family
            if(obsdat%cfamily(row_index) == family) then
               ! Add the row_index to the list
               list_index = list_index + 1
               index_list%indices(list_index) = row_index
            end if
         end do
         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
         index_list%indices(list_index+2)= -1 ! ... clearly
      end if ! list does not already exist

      index_list%current_element = 0    ! Set pointer to the start of the list
      depot%current_list => index_list  ! Note the current list
   end subroutine obs_set_current_header_list_from_family



   subroutine obs_set_current_header_list_all(obsdat) 1,1
      !
      ! PURPOSE:
      !      Find or create a row_index list for all headers and place it in the
      !      header depot.
      !
      ! author  : J.W. Blezius - 2014
      !
      implicit none
      type(struct_obs), intent(inout), target :: obsdat

      type(struct_index_list_depot), pointer :: depot
      type(struct_index_list), pointer :: index_list
      integer :: list, list_index, row_index

      nullify(index_list)
      depot => obsdat%header_index_list_depot

      ! Search for an existing list
      do list = 1, NUMBER_OF_LISTS
         if (depot%index_lists(list)%family == '  ') then
            index_list => depot%index_lists(list)
            index_list%current_element=0! Start at the beginning of the list
            exit                        ! Don't look any further
         end if
      end do

      ! If the list does not already exist
      if (.not. associated(index_list)) then
         ! Create a new list
         index_list => ild_get_empty_index_list(depot)
         index_list%family = '  '
         index_list%header = -1

         ! Populate the list
         list_index = 0
         do row_index = 1, obsdat%numHeader
            ! Add the row_index to the list
            list_index = list_index + 1
            index_list%indices(list_index) = row_index
         end do
         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
         index_list%indices(list_index+2)= -1 ! ... clearly
      end if ! list does not already exist

      index_list%current_element = 0    ! Set pointer to the start of the list
      depot%current_list => index_list  ! Note the current list
   end subroutine obs_set_current_header_list_all



   subroutine obs_setFamily(obsdat,Family_in,headerIndex_in,bodyIndex) 5,2
      !
      ! PURPOSE:
      !      Set to the indicated value the family for the indicated header, or
      !      else for the indicated body.
      !
      ! author  : J.W. Blezius - 2012
      !
      implicit none
      type(struct_obs), intent(inout) :: obsdat
      character(len=*), intent(in)    :: Family_in
      integer,optional, intent(in)    :: headerIndex_in,bodyIndex

      integer          :: headerIndex

      if(present(headerIndex_in)) then
         headerIndex=headerIndex_in
      elseif(present(bodyIndex)) then
         headerIndex=obs_bodyElem_i(obsdat,OBS_HIND,bodyIndex)
      else
         call obs_abort('OBS_SETFAMILY: Header or Body index must be specified!')
         return
      endif

      obsdat%cfamily(headerIndex)=Family_in
      if(headerIndex.eq.(obsdat%numHeader+1)) then
         obsdat%numHeader=obsdat%numHeader+1
      endif

   end subroutine obs_setFamily



   subroutine obs_status(obsdat, obs_full, numstns_out, numobs_out, kulout)
      !func obs_status - obtain basic status of the observation object
      !
      ! PURPOSE:
      !      Return the values of the object's status variables.
      !
      ! author  : J.W. Blezius - 2012
      !
      type (struct_obs), intent(in) :: obsdat
      logical, intent(out) :: obs_full
      integer, intent(out) :: numstns_out, numobs_out
      integer, intent(in)  :: kulout


      ! PLH   if ( obsdat%numHeader .ge. nmxobs ) then
      ! PLH     if ( obsdat%numBody .ge. ndatamx .or. obsdat%numHeader .ge. nmxobs ) then
      if (     obsdat%numBody   .ge. obsdat%numBody_max &
          .or. obsdat%numHeader .ge. obsdat%numHeader_max) then
         write(kulout,*) ' OBSDAT FILE FULL'
         obs_full = .true.

      else
         obs_full = .false.
      end if

      numstns_out = obsdat%numHeader
      numobs_out  = obsdat%numBody
   end subroutine obs_status



   subroutine obs_tosqlbdy(obsdat,kobs,kulout) 1,20
      !
      !s/r obs_tosqlbdybdy  - print all data records associated with a station
      !
      !authors  : Peter Houtekamer and Chantal Cote, July 2003. 
      !
      !arguments
      !     i   kobs  : no. of observation
      !     i   kulout: unit used for printing
      !
      implicit none

      type (struct_obs), intent(inout) :: obsdat
      integer,      intent(in) :: kobs,kulout

      integer :: idata,idata2,ihpht,ioer,ioma,iomp,iomp6,ipnt,ippp, &
         ivnm,ivnmc,istat,isigi, isigo,ivar,jdata,jtrans,var3d
      integer  :: mrbcol,mrbcvt
      real     :: rppp
      character(len=100) :: message
      external :: mrbcol,mrbcvt

      ipnt  = obs_headElem_i(obsdat, OBS_RLN, kobs)
      idata = obs_headElem_i(obsdat, OBS_NLV, kobs)

      do jdata = ipnt, ipnt + idata - 1
         idata2 = jdata -ipnt + 1
         if (btest(obs_bodyElem_i(obsdat, OBS_FLG, jdata),12)) then
            var3d=1
         else
            var3d=0
         endif

         ippp=obs_bodyElem_r(obsdat, OBS_PPP, jdata)
         rppp=float(ippp)

         ivnm=obs_bodyElem_i(obsdat, OBS_VNM, jdata)
         istat=mrbcol(ivnm,ivnmc,1) 
         istat=mrbcvt(ivnmc,ivar ,obs_bodyElem_r(obsdat, OBS_VAR, jdata),1,1,1,1)
         istat=mrbcvt(ivnmc,iomp ,obs_bodyElem_r(obsdat, OBS_OMP, jdata),1,1,1,1)
         istat=mrbcvt(ivnmc,iomp6,obs_bodyElem_r(obsdat, OBS_OMP6,jdata),1,1,1,1)
         istat=mrbcvt(ivnmc,ioma ,obs_bodyElem_r(obsdat, OBS_OMA, jdata),1,1,1,1)
         istat=mrbcvt(ivnmc,ioer ,obs_bodyElem_r(obsdat, OBS_OER, jdata),1,1,1,1)
         istat=mrbcvt(ivnmc,ihpht,obs_bodyElem_r(obsdat, OBS_HPHT,jdata),1,1,1,1)
         istat=mrbcvt(ivnmc,isigi,obs_bodyElem_r(obsdat, OBS_SIGI,jdata),1,1,1,1)
         istat=mrbcvt(ivnmc,isigo,obs_bodyElem_r(obsdat, OBS_SIGO,jdata),1,1,1,1)
         jtrans=obs_bodyElem_i(obsdat, OBS_VCO, jdata)
         if (jtrans .eq. 1) then
            istat=mrbcol(7001,ivnmc,1)
            istat=mrbcvt(ivnmc,ippp,rppp,1,1,1,1)
         elseif (jtrans .eq. 2) then
            istat=mrbcol(7004,ivnmc,1)
            istat=mrbcvt(ivnmc,ippp,rppp,1,1,1,1)
         elseif (jtrans .eq. 3) then
            istat=mrbcol(2150,ivnmc,1)
            istat=mrbcvt(ivnmc,ippp,rppp,1,1,1,1)
         else
            write(message,*) &
               'OBS_TOSQLBDY: attention, mauvaise coordonnee verticale, ', jtrans
            call obs_abort(message)
            return
         endif

         write(kulout,fmt=9201) kobs,idata2, &
            obs_bodyElem_i(obsdat, OBS_VNM, jdata),ippp, &
            obs_bodyElem_i(obsdat, OBS_ASS, jdata), &
            ivar,iomp,iomp6,ioma,ioer,ihpht,isigi,isigo,var3d,  &
            obs_bodyElem_r(obsdat, OBS_ZHA, jdata), &
            obs_bodyElem_i(obsdat, OBS_VCO, jdata), &
            obs_bodyElem_i(obsdat, OBS_FLG, jdata)
      enddo

9201  format(1x,i9,',',i3,2(',',i6),',',i3,8(',',i8), &
         ',',i2,',',f10.3,',',i2,',',i12)

      return
   end subroutine obs_tosqlbdy



   subroutine obs_tosqlhdr(obsdat,kobs,kulout) 1,15
      !
      !s/r obs_tosqlhdr  - printing of the header of a station record for sql
      !
      !author  : Peter Houtekamer and Chantal Cote, July 2003.
      !
      ! Revision July 2005 by Peter Houtekamer. Removed ncmblk from the OBSDAT.
      !
      !arguments
      !     i   kobs  : no. of observation
      !     i   kulout: unit used for output
      !
      implicit none

      type (struct_obs), intent(inout) :: obsdat
      integer, intent(in) :: kobs,kulout

      integer :: ialt,idburp,ii,ilon,ilat,iout,jtrans
      character(len=12) :: ccstnid
      real(8) :: torad 

      torad=4.d0*atan(1.d0)/180.d0

      ccstnid=obsdat%cstnid(kobs)

      ! Replace occasional appearance of "," by "b" in CCSTNID to avoid problem
      ! when converting this output to sqlite. - Xingxiu Deng, March 2009
      do
         iout=index(ccstnid,',')
         if (iout .gt. 0 ) then
            ccstnid(iout:iout)='b'
         else
            exit
         endif
      enddo

      ialt=obs_headElem_r(obsdat, OBS_ALT, kobs)+400
      ilon=nint((obs_headElem_r(obsdat, OBS_LON, kobs)/torad)*100.0)
      ilat=nint((obs_headElem_r(obsdat, OBS_LAT, kobs)/torad+90.0)*100.0)

      idburp=mod(obs_headElem_i(obsdat, OBS_ITY, kobs),1000)
      write(kulout,fmt=9200) kobs,CCSTNID, &
         obs_headElem_i(obsdat, OBS_DAT, kobs), &
         obs_headElem_i(obsdat, OBS_ETM, kobs), &
         obs_headElem_i(obsdat, OBS_RLN, kobs), &
         obs_headElem_i(obsdat, OBS_ONM, kobs), &
         obs_headElem_i(obsdat, OBS_INS, kobs), &
         obs_headElem_i(obsdat, OBS_OTP, kobs), &
         idburp,ilat,ilon,ialt,                &
         obs_headElem_i(obsdat, OBS_NLV, kobs), &
         obs_headElem_i(obsdat, OBS_OFL, kobs), &
         obs_headElem_i(obsdat, OBS_PAS, kobs), &
         obs_headElem_i(obsdat, OBS_REG, kobs), &
         obs_headElem_i(obsdat, OBS_IP , kobs)

9200  format(2x,i9,',',a9,',',i10,',',i8,',',i6,',',i6, &
         ',',i12,',',i6,4(',',i8),5(',',i6))

      return

   end subroutine obs_tosqlhdr



   subroutine obs_write(obsdat,hx, &,3
      nens,nobshdrout,nobsbdyout,nobshxout,nobsdimout)
      ! 
      ! PURPOSE: 
      !      Write the obsdat info to unformatted files.
      !
      !      Note that the body information is written in the order that it will
      !      be used by sekfeta.f
      !
      ! author  : Peter Houtekamer - February 2011
      !
      implicit none
      type(struct_obs), intent(in) :: obsdat
      real(8),      intent(in), dimension(:,:) :: hx
      integer,      intent(in) :: nens,nobshdrout,nobsbdyout, &
         nobshxout,nobsdimout

      integer :: irealBodies,jo,nrealBodies

      irealBodies=1
      do jo=1,obsdat%numHeader
         call obs_write_hdr(obsdat,jo,nobshdrout,irealBodies,nrealBodies)
         call obs_write_bdy(obsdat,jo,nobsbdyout)
         if (nens.gt.0) then
            call obs_write_hx(obsdat,hx,jo,nobshxout)
         endif
         irealBodies=irealBodies+nrealBodies
      enddo
      write(nobsdimout,*) obsdat%numHeader
      write(nobsdimout,*) irealBodies-1
      write(nobsdimout,*) nens

      return

   end subroutine obs_write



   subroutine obs_write_bdy(obsdat,kobs,kulout) 1,6
      !
      ! object  - write the data records associated with a
      !                 station in unformatted form.
      !
      !author  : P. Houtekamer  March 2000
      !
      !arguments
      !    input
      !     i   kobs  : no. of observation
      !     i   kulout: unit used for writing 
      !
      implicit none 

      type(struct_obs), intent(in) :: obsdat
      integer, intent(in) ::  kobs,kulout

      integer :: ipnt,idata,j,jdata,k


      ipnt  = obs_headElem_i(obsdat, OBS_RLN, kobs) 
      idata = obs_headElem_i(obsdat, OBS_NLV, kobs)

      ! write the data records
      do jdata=ipnt,ipnt+idata-1
         write(kulout) &
           (obsdat%intBodies%columns(odc_columnIndexFromActiveIndex( &
                                                obsdat%intBodies%odc_flavour,k) &
                                    )%value_i(jdata), &
                 k=1,odc_numActiveColumn(obsdat%intBodies)),&
           (obsdat%realBodies%columns(odc_columnIndexFromActiveIndex( &
                                               obsdat%realBodies%odc_flavour,j) &
                                     )%value_r(jdata), &
                 j=1,odc_numActiveColumn(obsdat%realBodies))
      enddo

      return

   end subroutine obs_write_bdy



   subroutine obs_write_hdr(obsdat,kobs,kulout,irealBodies,nrealBodies) 1,6
      !
      !object - writing of the header of a station record
      !
      !author  : Peter Houtekamer March 2000
      !
      !arguments
      !     i   kobs  : no. of observation
      !     i   kulout: unit used for output 
      !     i   irealBodies: location in the sorted realBodies
      !    output
      !     i   nrealBodies: number of observations for this header
      !
      implicit none

      type(struct_obs), intent(in) :: obsdat
      integer,      intent(in)  :: kobs,kulout,irealBodies
      integer,      intent(out) :: nrealBodies

      integer :: i,j


      ! (note that as a part of the writing, the body is being sorted
      !  so that the order of the observations in the body array 
      !  corresponds with the order of the headers in the header array).

      if(obsdat%mpi_local) then
         call obs_abort('obs_write_hdr() is not equipped to handle the ' // &
                        'case, mpi_local=.true.')
         return
      end if

      nrealBodies=obs_headElem_i(obsdat, OBS_NLV, kobs)
      ! write the header's content 
      write(kulout) irealBodies, &
            (obs_headElem_i &
                  (obsdat, &
                   odc_columnIndexFromActiveIndex(obsdat%intHeaders%odc_flavour,&
                                                  i), &
                   kobs &
                  ), &
             i=2,odc_numActiveColumn(obsdat%intHeaders) &
            ), &

            (obs_headElem_r &
                 (obsdat, &
                  odc_columnIndexFromActiveIndex(obsdat%realHeaders%odc_flavour,&
                                                 j), &
                  kobs &
                 ), &
             j=1,odc_numActiveColumn(obsdat%realHeaders) &
            ), &

            obsdat%cstnid(kobs), &
            obsdat%cfamily(kobs)

      return

   end subroutine obs_write_hdr



   subroutine obs_write_hx(obsdat,hx,kobs,kulout) 1,2
      !
      ! object  - write the interpolated values associated with a
      !                 station in unformatted form.
      !
      !author  : P. Houtekamer and H. Mitchell May 2005
      !
      !arguments
      !    input
      !        hx    : interpolated values    
      !        kobs  : no. of station 
      !        kulout: unit used for writing 
      !
      implicit none 

      type(struct_obs), intent(in) :: obsdat
      real(8), intent(in), dimension(:,:) :: hx
      integer, intent(in) :: kobs,kulout

      integer :: ipnt,idata,iens,j,jdata,k,nens

      nens = size(hx,1)

      ipnt  = obs_headElem_i(obsdat, OBS_RLN, kobs) 
      idata = obs_headElem_i(obsdat, OBS_NLV, kobs)

      ! write the data records
      do jdata=ipnt,ipnt+idata-1
         write(kulout) (hx(iens,jdata),iens=1,nens)
      enddo

      return

   end subroutine obs_write_hx

end module ObsSpaceData_mod