!WRF:MEDIATION_LAYER:IO
!  ---

! This obs-nudging FDDA module (RTFDDA) is developed by the 
! NCAR/RAL/NSAP (National Security Application Programs), under the 
! sponsorship of ATEC (Army Test and Evaluation Commands). ATEC is 
! acknowledged for releasing this capability for WRF community 
! research applications.
!
! The NCAR/RAL RTFDDA module was adapted, and significantly modified 
! from the obs-nudging module in the standard MM5V3.1 which was originally 
! developed by PSU (Stauffer and Seaman, 1994). 
! 
! Yubao Liu (NCAR/RAL): lead developer of the RTFDDA module 
! Al Bourgeois (NCAR/RAL): lead engineer implementing RTFDDA into WRF-ARW
! Nov. 2006
! 
! References:
!   
!   Liu, Y., A. Bourgeois, T. Warner, S. Swerdlin and J. Hacker, 2005: An
!     implementation of obs-nudging-based FDDA into WRF for supporting 
!     ATEC test operations. 2005 WRF user workshop. Paper 10.7.
!
!   Liu, Y., A. Bourgeois, T. Warner, S. Swerdlin and W. Yu, 2006: An update 
!     on "obs-nudging"-based FDDA for WRF-ARW: Verification using OSSE 
!     and performance of real-time forecasts. 2006 WRF user workshop. Paper 4.7. 

!   
!   Stauffer, D.R., and N.L. Seaman, 1994: Multi-scale four-dimensional data 
!     assimilation. J. Appl. Meteor., 33, 416-434.
!
!   http://www.rap.ucar.edu/projects/armyrange/references.html
!


  SUBROUTINE wrf_fddaobs_in (grid ,config_flags) 1,5

    USE module_domain
    USE module_configure
    USE module_model_constants        !rovg

    IMPLICIT NONE
    TYPE(domain) :: grid
    TYPE(grid_config_rec_type),  INTENT(IN)    :: config_flags
#if ( EM_CORE == 1 )

! Local variables
    integer            :: ktau            ! timestep index corresponding to xtime
    integer            :: krest           ! restart timestep
    integer            :: inest           ! nest level
    integer            :: infreq          ! input frequency
    integer            :: nstlev          ! nest level
    real               :: dtmin           ! dt in minutes
    real               :: xtime           ! forecast time in minutes
    logical            :: iprt_in4dob     ! print flag

    INTEGER ids , ide , jds , jde , kds , kde , &
            ims , ime , jms , jme , kms , kme , &
            ips , ipe , jps , jpe , kps , kpe
    INTEGER ij, its, ite, jts, jte

!   Modified to also call in4dob intially, since subr in4dob is no
!   longer called from subr fddaobs_init. Note that itimestep is now
!   the model step BEFORE the model integration step, because this
!   routine is now called by med_before_solve_io.
    ktau   = grid%itimestep               ! ktau corresponds to xtime
    krest  = grid%fdob%ktaur
    inest  = grid%grid_id
    nstlev = grid%fdob%levidn(inest) 
    infreq = grid%obs_ionf*(grid%parent_grid_ratio**nstlev)
    iprt_in4dob = grid%obs_ipf_in4dob

    IF( ((ktau.GT.krest.AND.MOD(ktau,infreq).EQ.0)                            &
                                         .OR.(ktau.EQ.krest)) .AND. grid%xtime <= grid%fdda_end ) then
! Calculate forecast time.
      dtmin = grid%dt/60.
      xtime = grid%xtime

      CALL get_ijk_from_grid (  grid ,                                       &
                                ids, ide, jds, jde, kds, kde,                &
                                ims, ime, jms, jme, kms, kme,                &
                                ips, ipe, jps, jpe, kps, kpe    )

      !$OMP PARALLEL DO   &
      !$OMP PRIVATE ( ij )

      DO ij = 1 , grid%num_tiles
         its = grid%i_start(ij)
         ite = min(grid%i_end(ij),ide-1)
         jts = grid%j_start(ij)
         jte = min(grid%j_end(ij),jde-1)

         CALL in4dob(inest, xtime, ktau, krest, dtmin,                              &
                     grid%julyr, grid%julday, grid%gmt,                             &    !obsnypatch
                     grid%obs_nudge_opt,  grid%obs_nudge_wind, grid%obs_nudge_temp, &
                     grid%obs_nudge_mois, grid%obs_nudge_pstr, grid%obs_coef_wind,  &
                     grid%obs_coef_temp,  grid%obs_coef_mois,  grid%obs_coef_pstr,  &
                     grid%obs_rinxy,      grid%obs_rinsig,     grid%fdob%window,    &
                     grid%obs_npfi,       grid%obs_ionf,                            &
                     grid%obs_prt_max,    grid%obs_prt_freq,                        &
                     grid%obs_idynin,                                               &
                     grid%obs_dtramp,     grid%fdob,           grid%fdob%varobs,    &
                     grid%fdob%timeob,    grid%fdob%nlevs_ob,  grid%fdob%lev_in_ob, &
                     grid%fdob%plfo,      grid%fdob%elevob,    grid%fdob%rio,       &
                     grid%fdob%rjo,       grid%fdob%rko,                            &
                     grid%xlat, grid%xlong,                                         &
                     config_flags%cen_lat,                                          &
                     config_flags%cen_lon,                                          &
                     config_flags%stand_lon,                                        &
                     config_flags%truelat1, config_flags%truelat2,                  &
                     grid%fdob%known_lat, grid%fdob%known_lon,                      &
                     config_flags%dx, config_flags%dy, rovg, t0,                    &
                     grid%fdob%obsprt,                                              &
                     grid%fdob%latprt, grid%fdob%lonprt,                            &
                     grid%fdob%mlatprt, grid%fdob%mlonprt,                          &
                     grid%fdob%stnidprt,                                            &
                     ide, jde,                                                      &
                     ims, ime, jms, jme,                                            &
                     its, ite, jts, jte,                                            &
                     config_flags%map_proj,                                         &
                     model_config_rec%parent_grid_ratio,                            &
                     model_config_rec%i_parent_start(inest),                        &
                     model_config_rec%j_parent_start(inest),                        &
                     model_config_rec%max_dom,                                      &
                     model_config_rec%nobs_ndg_vars, grid%max_obs, iprt_in4dob)
       ENDDO
      !$OMP END PARALLEL DO

    ENDIF

    RETURN
#endif
  END SUBROUTINE wrf_fddaobs_in
#if ( EM_CORE == 1 )
!------------------------------------------------------------------------------
! Begin subroutine in4dob and its subroutines
!------------------------------------------------------------------------------

  SUBROUTINE in4dob(inest, xtime, ktau, ktaur, dtmin,                    & 1,68
                    myear, julday, gmt,                                  &      !obsnypatch
                    nudge_opt, iswind, istemp,                           &
                    ismois, ispstr, giv,                                 &
                    git, giq, gip,                                       &
                    rinxy, rinsig, twindo,                               &
                    npfi, ionf, prt_max, prt_freq, idynin,               &
                    dtramp, fdob, varobs,                                &
                    timeob, nlevs_ob, lev_in_ob,                         &
                    plfo, elevob, rio,                                   &
                    rjo, rko,                                            &
                    xlat, xlong,                                         &
                    cen_lat,                                             &
                    cen_lon,                                             &
                    stand_lon,                                           &
                    true_lat1, true_lat2,                                &
                    known_lat, known_lon,                                &
                    dxm, dym, rovg, t0,                                  &
                    obs_prt,                                             &
                    lat_prt, lon_prt,                                    &
                    mlat_prt, mlon_prt,                                  &
                    stnid_prt,                                           &
                    e_we, e_sn,                                          &
                    ims, ime, jms, jme,                                  &
                    its, ite, jts, jte,                                  &
                    map_proj,                                            &
                    parent_grid_ratio,                                   &
                    i_parent_start,                                      &
                    j_parent_start,                                      &
                    maxdom,                                              &
                    nndgv, niobf, iprt)

  USE module_domain
  USE module_model_constants, ONLY : rcp
  USE module_date_time , ONLY : geth_idts
  USE module_llxy

  IMPLICIT NONE

! THIS IS SUBROUTINE READS AN OBSERVATION DATA FILE AND
! SELECTS ONLY THOSE VALUES OBSERVED AT TIMES THAT FALL
! WITHIN A TIME WINDOW (TWINDO) CENTERED ABOUT THE CURRENT
! FORECAST TIME (XTIME).  THE INCOMING OBS FILES MUST BE
! IN CHRONOLOGICAL ORDER.
!
! NOTE: This routine was originally designed for MM5, which uses
!       a nonstandard (I,J) coordinate system. For WRF, I is the 
!       east-west running coordinate, and J is the south-north
!       running coordinate. So "J-slab" here is west-east in
!       extent, not south-north as for MM5. RIO and RJO have
!       the opposite orientation here as for MM5. -ajb 06/10/2004

! NOTE - IN4DOB IS CALLED ONLY FOR THE COARSE MESH TIMES
!      - VAROBS(IVAR,N) HOLDS THE OBSERVATIONS.
!        IVAR=1   OBS U
!        IVAR=2   OBS V
!        IVAR=3   OBS T
!        IVAR=4   OBS Q
!        IVAR=5   OBS Pressure
!        IVAR=6   OBS Height

  INTEGER, intent(in) :: niobf          ! maximum number of observations
  INTEGER, intent(in) :: nndgv          ! number of nudge variables
  INTEGER, intent(in)  :: INEST         ! nest level
  REAL, intent(in)     :: xtime         ! model time in minutes
  INTEGER, intent(in)  :: KTAU          ! current timestep
  INTEGER, intent(in)  :: KTAUR         ! restart timestep
  REAL, intent(in)     :: dtmin         ! dt in minutes
  INTEGER, intent(in)  :: myear         ! model year                           !obsnypatch
  INTEGER, intent(in)  :: julday        ! Julian day
  REAL, intent(in)     :: gmt           ! Model GMT at start of run
  INTEGER, intent(in)  :: nudge_opt     ! obs-nudge flag for this nest
  INTEGER, intent(in)  :: iswind        ! nudge flag for wind
  INTEGER, intent(in)  :: istemp        ! nudge flag for temperature
  INTEGER, intent(in)  :: ismois        ! nudge flag for moisture
  INTEGER, intent(in)  :: ispstr        ! nudge flag for pressure (obsolete)
  REAL, intent(in)     :: giv           ! coefficient for wind
  REAL, intent(in)     :: git           ! coefficient for temperature
  REAL, intent(in)     :: giq           ! coefficient for moisture
  REAL, intent(in)     :: gip           ! coefficient for pressure
  REAL, intent(in)     :: rinxy         ! horizontal radius of influence (km)
  REAL, intent(in)     :: rinsig        ! vertical radius of influence (on sigma)
  REAL, intent(inout)  :: twindo        ! (time window)/2 (min) for nudging
  INTEGER, intent(in)  :: npfi          ! coarse-grid time-step frequency for diagnostics
  INTEGER, intent(in)  :: ionf          ! coarse-grid time-step frequency for obs-nudging calcs
  INTEGER, intent(in)  :: prt_max       ! max number of entries of obs for diagnostic printout
  INTEGER, intent(in)  :: prt_freq      ! frequency (in obs index) for diagnostic printout.
  INTEGER, intent(in)  :: idynin        ! for dynamic initialization using a ramp-down function
  REAL, intent(in)     :: dtramp        ! time period in minutes for ramping
  TYPE(fdob_type), intent(inout)  :: fdob     ! derived data type for obs data
  REAL, intent(inout) :: varobs(nndgv,niobf)  ! observational values in each variable
  REAL, intent(inout) :: timeob(niobf)        ! model times for each observation (hours)
  REAL, intent(inout) :: nlevs_ob(niobf)      ! numbers of levels in sounding obs
  REAL, intent(inout) :: lev_in_ob(niobf)     ! level in sounding-type obs
  REAL, intent(inout) :: plfo(niobf)          ! index for type of obs-platform
  REAL, intent(inout) :: elevob(niobf)        ! elevations of observations  (meters)
  REAL, intent(inout) :: rio(niobf)           ! west-east grid coordinate (non-staggered grid)
  REAL, intent(inout) :: rjo(niobf)           ! south-north grid coordinate (non-staggered grid)
  REAL, intent(inout) :: rko(niobf)           ! vertical grid coordinate
  REAL, DIMENSION( ims:ime, jms:jme ),                            &
        INTENT(IN )       :: xlat, xlong      ! lat/lon on mass-pt grid (for diagnostics only)
  REAL, intent(in) :: cen_lat                 ! center latitude for map projection
  REAL, intent(in) :: cen_lon                 ! center longitude for map projection
  REAL, intent(in) :: stand_lon               ! standard longitude for map projection
  REAL, intent(in) :: true_lat1               ! truelat1 for map projection
  REAL, intent(in) :: true_lat2               ! truelat2 for map projection
  REAL, intent(in) :: known_lat               ! latitude  of domain origin point (i,j)=(1,1) 
  REAL, intent(in) :: known_lon               ! longigude of domain origin point (i,j)=(1,1)
  REAL, intent(in) :: dxm                     ! grid size in x (meters)
  REAL, intent(in) :: dym                     ! grid size in y (meters)
  REAL, intent(in) :: rovg                    ! constant rho over g
  REAL, intent(in) :: t0                      ! background temperature
  INTEGER, intent(inout) :: obs_prt(prt_max)  ! For printout of obs index
  REAL, intent(inout) :: lat_prt(prt_max)     ! For printout of obs latitude 
  REAL, intent(inout) :: lon_prt(prt_max)     ! For printout of obs longitude
  REAL, intent(inout) :: mlat_prt(prt_max)    ! For printout of model lat at obs (ri,rj)
  REAL, intent(inout) :: mlon_prt(prt_max)    ! For printout of model lon at obs (ri,rj)
  INTEGER, intent(inout) :: stnid_prt(40,prt_max) ! For printout of model lon at obs (ri,rj)
  INTEGER, intent(in) :: e_we                 ! max grid index in south-north coordinate
  INTEGER, intent(in) :: e_sn                 ! max grid index in west-east   coordinate
  INTEGER, intent(in) :: ims                  ! grid memory start index (west-east dim)
  INTEGER, intent(in) :: ime                  ! grid memory end   index (west-east dim)
  INTEGER, intent(in) :: jms                  ! grid memory start index (south-north dim)
  INTEGER, intent(in) :: jme                  ! grid memory end   index (south-north dim)
  INTEGER, intent(in) :: its                  ! grid tile   start index (west-east dim)
  INTEGER, intent(in) :: ite                  ! grid tile   end   index (west-east dim)
  INTEGER, intent(in) :: jts                  ! grid tile   start index (south-north dim)
  INTEGER, intent(in) :: jte                  ! grid tile   end   index (south-north dim)
  INTEGER, intent(in) :: map_proj             ! map projection index
  INTEGER, intent(in) :: parent_grid_ratio    ! parent to nest grid ration
  INTEGER, intent(in) :: i_parent_start       ! starting i coordinate in parent domain
  INTEGER, intent(in) :: j_parent_start       ! starting j coordinate in parent domain
  INTEGER, intent(in) :: maxdom               ! maximum number of domains
  LOGICAL, intent(in) :: iprt                 ! print flag
      
!***  DECLARATIONS FOR IMPLICIT NONE                                    
  integer :: n, ndum, nopen, nvol, idate, imm, iss
  integer :: nlast                      ! last obs in list of valid obs from prev window
  integer :: nsta                       ! number of stations held in timeobs array 
  integer :: nstaw                      ! # stations within the actual time window
  integer :: nprev                      ! previous n in obs read loop (for printout only)
  integer :: meas_count, imc, njend, njc, njcc, julob, kn
  real    :: hourob, rjulob
  real    :: xhour, tback, tforwd, rjdate1, timanl1, rtimob
  real    :: rj, ri, elevation, pressure_data
  real    :: pressure_qc, height_data, height_qc, temperature_data
  real    :: temperature_qc, u_met_data, u_met_qc, v_met_data
  real    :: v_met_qc, rh_data, rh_qc, r_data, slp_data, slp_qc
  real    :: ref_pres_data, ref_pres_qc, psfc_data, psfc_qc
  real    :: precip_data, precip_qc, tbar, twdop
  real*8  :: tempob
  INTEGER, EXTERNAL :: nvals_le_limit         ! for finding #obs with timeobs <= tforwd

! Local variables
  TYPE (PROJ_INFO)   :: obs_proj        ! Structure for obs projection information.
  character*14 date_char
  character*19 obs_date                                                        !obsnypatch
  integer idts                                                                 !obsnypatch
  character*40 platform,source,id,namef
  character*2 fonc
  character(len=200) :: msg       ! Argument to wrf_message
  real latitude,longitude
  logical :: newpass          ! New pass flag (used for printout)
  logical is_sound,bogus
  LOGICAL OPENED,exist
  integer :: ieof(5),ifon(5)
  data ieof/0,0,0,0,0/
  data ifon/0,0,0,0,0/
  integer :: nmove, nvola
  integer :: iyear, itimob                                                     !obsnypatch
  integer :: errcnt
  DATA NMOVE/0/,NVOLA/61/
  character*140 file_name_on_obs_unit, obs_domain_file_name
  integer :: obs_domain_file_unit

! if(ieof(inest).eq.2.and.fdob%nstat.eq.0)then
!   IF (iprt) print *,'returning from in4dob'
!   return
! endif
! IF (iprt) print *,'start in4dob ',inest,xtime
  IF(nudge_opt.NE.1)RETURN

! Initialize obs for printout
  obs_prt = -999
  newpass = .true.
  errcnt  = 0 

! if start time, or restart time, set obs array to missing value
  IF(KTAU.EQ.0.OR.KTAU.EQ.KTAUR) THEN
    DO N=1,NIOBF
      TIMEOB(N)=99999.
    ENDDO
    fdob%xtime_at_rest = xtime    !yliu 20080127
  ENDIF
! set number of obs=0 if at start or restart
  IF(KTAU.EQ.KTAUR)fdob%NSTAT=0
  NSTA=fdob%NSTAT

  XHOUR=XTIME/60.
  XHOUR=AMAX1(XHOUR,0.0)

! DEFINE THE MAX LIMITS OF THE WINDOW
  TBACK=XHOUR-TWINDO
  TFORWD=XHOUR+TWINDO

  IF (iprt) then
     write(msg,fmt='(2(a,f8.3),a,i2)')                                            &
                  'OBS NUDGING: Reading new obs for time window TBACK = ',  &
                  tback,' TFORWD = ',tforwd,' for grid = ',inest
     call wrf_message(msg)
  ENDIF

! For obs that have become invalid because they are too old for the current time
! window, mark with 99999 to set up for removal from the rolling valid-obs list.

  IF(NSTA.NE.0) THEN
    NDUM=0
    t_window : DO N=1,NSTA+1
      IF((TIMEOB(N)-TBACK).LT.0) THEN
        TIMEOB(N)=99999.
      ENDIF
      IF(TIMEOB(N).LT.9.E4) EXIT t_window
      NDUM=N
    ENDDO t_window

    IF (iprt .and. ndum>0) THEN
      write(msg,fmt='(a,i5,2a)') 'OBS NUDGING: ',ndum,' previously read obs ',  &
           'are now too old for the current window and have been removed.'
      call wrf_message(msg)
    ENDIF

! REMOVE OLD OBS DENOTED BY 99999. AT THE FRONT OF TIMEOB ARRAY
!   IF (iprt) print *,'ndum at 20=',ndum
    NDUM=ABS(NDUM)
    NMOVE=NIOBF-NDUM
    IF(NMOVE.GT.0 .AND. NDUM.NE.0 ) THEN  
      DO N=1,NMOVE
        do KN = 1,nndgv
          VAROBS(KN,N)=VAROBS(KN,N+NDUM)
        enddo
! RIO is the west-east coordinate. RJO is south-north. (ajb)
        RJO(N)=RJO(N+NDUM)
        RIO(N)=RIO(N+NDUM)
        RKO(N)=RKO(N+NDUM)
        TIMEOB(N)=TIMEOB(N+NDUM)
        nlevs_ob(n)=nlevs_ob(n+ndum)
        lev_in_ob(n)=lev_in_ob(n+ndum)
        plfo(n)=plfo(n+ndum)
        elevob(n)=elevob(n+ndum) 
      ENDDO
    ENDIF
    NOPEN=NMOVE+1
    IF(NOPEN.LE.NIOBF) THEN
      DO N=NOPEN,NIOBF
        do KN = 1,nndgv
          VAROBS(KN,N)=99999.
        enddo
        RIO(N)=99999.
        RJO(N)=99999.
        RKO(N)=99999.
        TIMEOB(N)=99999.
        nlevs_ob(n)=99999.
        lev_in_ob(n)=99999.
        plfo(n)=99999.
        elevob(n)=99999.
      ENDDO
    ENDIF
  ENDIF

! Compute map projection info.
  call set_projection(obs_proj, map_proj, cen_lat, cen_lon,            &
                      true_lat1, true_lat2, stand_lon,                 &
                      known_lat, known_lon,                            &
                      e_we, e_sn, dxm, dym )

! FIND THE LAST OBS IN THE LIST
  NLAST=0
  last_ob : DO N=1,NIOBF
!   print *,'nlast,n,timeob(n)=',nlast,n,timeob(n)
    IF(TIMEOB(N).GT.9.E4) EXIT last_ob
    NLAST=N
  ENDDO last_ob

! print *,'in4dob, after 90 ',nlast,ktau,ktaur,nsta
! open file if at beginning or restart
  IF(KTAU.EQ.0.OR.KTAU.EQ.KTAUR) THEN
    fdob%RTLAST=-999.
    obs_domain_file_unit = NVOLA+INEST-1
    INQUIRE (obs_domain_file_unit,NAME=file_name_on_obs_unit,OPENED=OPENED)
    !Build obs nudging file name (OBS_DOMAINX01 where X is the current domain number)
      ifon(inest)=1
      write(fonc(1:2),'(i2)')ifon(inest)
      if(fonc(1:1).eq.' ')fonc(1:1)='0'
    obs_domain_file_name='OBS_DOMAIN'//CHAR(INEST+ICHAR('0'))//fonc(1:2)  
    IF (.NOT. OPENED) THEN
      INQUIRE (file=obs_domain_file_name,EXIST=exist)
      if(exist)then
        IF (iprt) THEN
          write(msg,*) 'opening first fdda obs file, fonc=',              &
                       fonc,' inest=',inest
          call wrf_message(msg)
          write(msg,*) 'ifon=',ifon(inest)
          call wrf_message(msg)
        ENDIF
        OPEN(obs_domain_file_unit,FILE=obs_domain_file_name,FORM='FORMATTED',STATUS='OLD')
      else
! no first file to open
        IF (iprt) call wrf_message("there are no fdda obs files to open")
        return
      endif
    ELSE
     !If the unit for observation nudging file is already open make sure the file
     !name matches the expected name to ensure that some other part of WRF has
     !not opened a file using the same unit number
     IF(file_name_on_obs_unit .ne. obs_domain_file_name) THEN 
      write(msg,*) 'File open on obs nudging unit (',obs_domain_file_unit,') with wrong name'
      call wrf_message(msg)
      write(msg,*) 'File open on obs nudging unit is named ',&
       trim(adjustl(file_name_on_obs_unit)),' but it should be named ',&
       trim(adjustl(obs_domain_file_name))
      call wrf_message(msg)
      write(msg,*) 'This likely means this unit number was opened elsewhere in WRF'
      call wrf_message(msg)
      call wrf_error_fatal ( 'wrf_fddaobs_in: in4dob STOP Obs nudging file name mismatch' )
     ENDIF
    ENDIF
  ENDIF  !end if(KTAU.EQ.0.OR.KTAU.EQ.KTAUR)
! print *,'at jc check1'
 
!**********************************************************************
!       --------------   BIG 100 LOOP OVER N  --------------
!**********************************************************************
! NOW CHECK TO SEE IF EXTRA DATA MUST BE READ IN FROM THE
! DATA FILE.  CONTINUE READING UNTIL THE REACHING THE EOF
! (DATA TIME IS NEGATIVE) OR FIRST TIME PAST TFORWD. THE
! LAST OBS CURRENTLY AVAILABLE IS IN N=NMOVE.

  N=NLAST
  IF(N.EQ.0)GOTO 110

 1001 continue

! ieof=2 means no more files

    IF(IEOF(inest).GT.1) then
      GOTO 130
    endif

100 CONTINUE
!ajb 20070116 bugfix for zero array index. N=0 if first obs is not in the domain.
    IF(N.ne.0) THEN
      IF(TIMEOB(N).GT.TFORWD.and.timeob(n).lt.99999.) THEN
         GOTO 130
      ENDIF
    ENDIF
 
! OBSFILE: no more data in the obsfile 
! AJB note: This is where we would implement multi-file reading.
    if(ieof(inest).eq.1 )then
      ieof(inest)=2
      goto 130
    endif

!**********************************************************************
!       --------------   110 SUBLOOP OVER N  --------------
!**********************************************************************
  110 continue
! THE TIME OF THE MOST RECENTLY ACQUIRED OBS IS .LE. TFORWD,
! SO CONTINUE READING

      IF(N.GT.NIOBF-1)GOTO 120
! REPLACE NVOLA WITH LUN 70, AND USE NVOLA AS A FILE COUNTER
      NVOL=NVOLA+INEST-1
      IF(fdob%IEODI.EQ.1)GOTO 111
      read(nvol,101,end=111,err=111)date_char
 101  FORMAT(1x,a14)

      n=n+1

! Convert the form of the observation date for geth_idts.
      call fmt_date(date_char, obs_date)

! Compute the time period in seconds from the model reference
! date (fdob%sdate) until the observation date.

      call geth_idts(obs_date, fdob%sdate(1:19), idts)

! Convert time in seconds to hours.
      ! In case of restart, correct for new sdate.
      idts = idts + nint(fdob%xtime_at_rest*60.)  ! yliu 20080127

      rtimob =float(idts)/3600.
      timeob(n)=rtimob

!     print *,'read in ob ',n,timeob(n),rtimob
      IF(IDYNIN.EQ.1.AND.TIMEOB(N)*60..GT.fdob%DATEND) THEN
        IF (iprt) THEN
          write(msg,*) ' IN4DOB: FOR INEST = ',INEST,' AT XTIME = ',XTIME,    &
                       ' TIMEOB = ',TIMEOB(N)*60.,' AND DATEND = ',fdob%DATEND,' :'
          call wrf_message(msg)
          write(msg,*) '         END-OF-DATA FLAG SET FOR OBS-NUDGING',       &
                       ' DYNAMIC INITIALIZATION'
          call wrf_message(msg)
        ENDIF
        fdob%IEODI=1
        TIMEOB(N)=99999.
        rtimob=timeob(n)
      ENDIF
      read(nvol,102)latitude,longitude
 102  FORMAT(2x,2(f9.4,1x))

!     if(ifon.eq.4)print *,'ifon=4',latitude,longitude
! this works only for lc projection
! yliu: add llxy for all 3 projection
          
!ajb Arguments ri and rj have been switched from MM5 orientation.

      CALL latlon_to_ij(obs_proj, latitude, longitude, ri, rj)

!ajb  ri and rj are referenced to the non-staggered grid (not mass-pt!).
!     (For MM5, they were referenced to the dot grid.)

      ri = ri + .5      !ajb Adjust from mass-pt to non-staggered grid.
      rj = rj + .5      !ajb Adjust from mass-pt to non-staggered grid.

      rio(n)=ri
      rjo(n)=rj

      read(nvol,1021)id,namef
 1021 FORMAT(2x,2(a40,3x))
      read(nvol,103)platform,source,elevation,is_sound,bogus,meas_count
 103  FORMAT( 2x,2(a16,2x),f8.0,2x,2(l4,2x),i5)

!     write(6,*) '----- OBS description ----- '
!     write(6,*) 'platform,source,elevation,is_sound,bogus,meas_count:'
!     write(6,*) platform,source,elevation,is_sound,bogus,meas_count

! yliu 
      elevob(n)=elevation
! jc
! change platform from synop to profiler when needed
      if(namef(2:9).eq.'PROFILER')platform(7:14)='PROFILER'
! yliu
      if(namef(2:6).eq.'ACARS')platform(7:11)='ACARS'
      if(namef(1:7).eq.'SATWNDS') platform(1:11)='SATWNDS    '
      if(namef(1:8).eq.'CLASS DA')platform(7:10)='TEMP'
! yliu end
 
      rko(n)=-99.
!yliu 20050706
!     if((platform(7:11).eq.'METAR').or.(platform(7:11).eq.'SPECI').or.
!    1   (platform(7:10).eq.'SHIP').or.(platform(7:11).eq.'SYNOP').or.
!    1    (platform(1:4).eq.'SAMS'))
!    1   rko(n)=1.0
      if(.NOT. is_sound) rko(n)=1.0
!yliu 20050706 end

! plfo is inFORMATion on what platform. May use this later in adjusting weights
      plfo(n)=99.
      if(platform(7:11).eq.'METAR')plfo(n)=1.
      if(platform(7:11).eq.'SPECI')plfo(n)=2.
      if(platform(7:10).eq.'SHIP')plfo(n)=3.
      if(platform(7:11).eq.'SYNOP')plfo(n)=4.
      if(platform(7:10).eq.'TEMP')plfo(n)=5.
      if(platform(7:11).eq.'PILOT')plfo(n)=6.
      if(platform(1:7).eq.'SATWNDS')plfo(n)=7.
      if(platform(7:11).eq.'SATWI')plfo(n)=7.
      if(platform(1:4).eq.'SAMS')plfo(n)=8.
      if(platform(7:14).eq.'PROFILER')plfo(n)=9.
! yliu: ACARS->SATWINDS
      if(platform(7:11).eq.'ACARS')plfo(n)=7.
! yliu: end
      if(plfo(n).eq.99.) then
         IF (iprt) then
           write(msg,*) 'n=',n,' unknown ob of type ',platform
           call wrf_message(msg)
         ENDIF
      endif

!======================================================================
!======================================================================
! THIS PART READS SOUNDING INFO
      IF(is_sound)THEN
        nlevs_ob(n)=real(meas_count)
        lev_in_ob(n)=1.
        do imc=1,meas_count
!             write(6,*) '0 inest = ',inest,' n = ',n
! the sounding has one header, many levels. This part puts it into 
! "individual" observations. There's no other way for nudob to deal
! with it.
          if(imc.gt.1)then                          ! sub-loop over N
            n=n+1
            if(n.gt.niobf)goto 120
            nlevs_ob(n)=real(meas_count)
            lev_in_ob(n)=real(imc)
            timeob(n)=rtimob
            rio(n)=ri
            rjo(n)=rj
            rko(n)=-99.
            plfo(n)=plfo(n-imc+1)
            elevob(n)=elevation
          endif

          read(nvol,104)pressure_data,pressure_qc,                  &
                        height_data,height_qc,                      &
                        temperature_data,temperature_qc,            &
                        u_met_data,u_met_qc,                        &
                        v_met_data,v_met_qc,                        &
                        rh_data,rh_qc
 104      FORMAT( 1x,6(f11.3,1x,f11.3,1x))

! yliu: Ensemble - add disturbance to upr obs
!         if(plfo(n).eq.5.or.plfo(n).eq.6.or.plfo(n).eq.9) then                  FORE07E08
!          if(imc.eq.1) then                                                     FORE07E08
!     call srand(n)
!     t_rand =- (rand(2)-0.5)*6
!     call srand(n+100000)
!     u_rand =- (rand(2)-0.5)*6
!     call srand(n+200000)
!     v_rand =- (rand(2)-0.5)*6
!          endif                                                                 FORE07E08
!     if(temperature_qc.ge.0..and.temperature_qc.lt.30000..and.
!    &   temperature_data .gt. -88880.0 )
!    & temperature_data = temperature_data  + t_rand
!     if((u_met_qc.ge.0..and.u_met_qc.lt.30000.).and.
!    &   (v_met_qc.ge.0..and.v_met_qc.lt.30000.).and.
! make sure at least 1 of the components is .ne.0
!    &   (u_met_data.ne.0..or.v_met_data.ne.0.) .and.
!    &   (u_met_data.gt.-88880.0 .and. v_met_data.gt.-88880.0) )then
!         u_met_data = u_met_data + u_rand
!         v_met_data = v_met_data + v_rand
!     endif
!         endif                                                                  FORE07E08
! yliu: Ens test - end

 
! jc
! hardwire to switch -777777. qc to 0. here temporarily
! -777777. is a sounding level that no qc was done on.
 
          if(temperature_qc.eq.-777777.)temperature_qc=0.
          if(pressure_qc.eq.-777777.)pressure_qc=0.
          if(height_qc.eq.-777777.)height_qc=0.
          if(u_met_qc.eq.-777777.)u_met_qc=0.
          if(v_met_qc.eq.-777777.)v_met_qc=0.
          if(rh_qc.eq.-777777.)rh_qc=0.
          if(temperature_data.eq.-888888.)temperature_qc=-888888.
          if(pressure_data.eq.-888888.)pressure_qc=-888888.
          if(height_data.eq.-888888.)height_qc=-888888.
          if(u_met_data.eq.-888888.)u_met_qc=-888888.
          if(v_met_data.eq.-888888.)v_met_qc=-888888.
          if(rh_data.eq.-888888.)rh_qc=-888888.
 
! jc
! Hardwire so that only use winds in pilot obs (no winds from temp) and
!    only use temperatures and rh in temp obs (no temps from pilot obs)
! Do this because temp and pilot are treated as 2 platforms, but pilot 
!    has most of the winds, and temp has most of the temps. If use both,
!    the second will smooth the effect of the first. Usually temps come in after
!    pilots. pilots usually don't have any temps, but temp obs do have some 
!    winds usually.
! plfo=5 is TEMP ob, range sounding is an exception
!yliu start -- comment out to test with merged PILOT and TEMP and 
!        do not use obs interpolated by little_r
!       if(plfo(n).eq.5. .and. namef(1:8).ne.'CLASS DA')then
!         u_met_data=-888888.
!         v_met_data=-888888.
!         u_met_qc=-888888.
!         v_met_qc=-888888.
!       endif
          if(plfo(n).eq.5..and.(u_met_qc.eq.256..or.v_met_qc.eq.256.))then
            u_met_data=-888888.
            v_met_data=-888888.
            u_met_qc=-888888.
            v_met_qc=-888888.
          endif
!yliu end
! plfo=6 is PILOT ob
          if(plfo(n).eq.6.)then
            temperature_data=-888888.
            rh_data=-888888.
            temperature_qc=-888888.
            rh_qc=-888888.
          endif

!ajb Store temperature for WRF
!    NOTE: The conversion to potential temperature, performed later in subroutine
!    errob, requires good pressure data, either directly or via good height data.
!    So here, in addition to checking for good temperature data,  we must also
!    do a check for good pressure or height.
          if(temperature_qc.ge.0..and.temperature_qc.lt.30000.)then

            if( (pressure_qc.ge.0..and.pressure_qc.lt.30000.) .or.    &
                (height_qc  .ge.0..and.height_qc  .lt.30000.) ) then

              varobs(3,n) = temperature_data
            else
              varobs(3,n)=-888888.
            endif

          else
            varobs(3,n)=-888888.
          endif

!ajb Store obs height
          if(height_qc.ge.0..and.height_qc.lt.30000.)then
            varobs(6,n)=height_data
          else
            varobs(6,n)=-888888.
          endif

          if(pressure_qc.ge.0..and.pressure_qc.lt.30000.)then
!           if(pressure_qc.ge.0.)then
            varobs(5,n)=pressure_data
          else
            varobs(5,n)=-888888.
            IF (iprt) THEN
              if(varobs(6,n).eq.-888888.000) then
                if (errcnt.le.10) then
                  write(msg,*) '*** PROBLEM: sounding, p and ht undefined',latitude,longitude
                  call wrf_message(msg)
                  errcnt = errcnt + 1
                  if (errcnt.gt.10) call wrf_message("MAX of 10 warnings issued.")
                endif
              endif
            ENDIF
          endif 
          if(varobs(5,n).ge.0.)varobs(5,n)=varobs(5,n)*1.e-3
! don't use data above 80 mb
          if((varobs(5,n).gt.0.).and.(varobs(5,n).le.8.))then
            u_met_data=-888888.
            v_met_data=-888888.
            u_met_qc=-888888.
            v_met_qc=-888888.
            temperature_data=-888888.
            temperature_qc=-888888.
            rh_data=-888888.
            rh_qc=-888888.
          endif


! Store horizontal wind components for WRF
          if((u_met_qc.ge.0..and.u_met_qc.lt.30000.).and.  &
             (v_met_qc.ge.0..and.v_met_qc.lt.30000.).and.  &
! make sure at least 1 of the components is .ne.0
             (u_met_data.ne.0..or.v_met_data.ne.0.))then

! If Earth-relative wind vector, need to rotate it to grid-relative coords
               if(u_met_qc.eq.129. .and. v_met_qc.eq.129.) then
                  CALL rotate_vector(longitude,u_met_data,v_met_data,   &
                                     obs_proj,map_proj)
               endif
               varobs(1,n)=u_met_data
               varobs(2,n)=v_met_data
          else
               varobs(1,n)=-888888.
               varobs(2,n)=-888888.
          endif

          r_data=-888888.

          if(rh_qc.ge.0..and.rh_qc.lt.30000.)then
            if((pressure_qc.ge.0.).and.(temperature_qc.ge.0.).and.       &
              (pressure_qc.lt.30000.).and.(temperature_qc.lt.30000.))then
              call rh2r(rh_data,temperature_data,pressure_data*.01,      &
                        r_data,0)            ! yliu, change last arg from 1 to 0
            else
!             print *,'rh, but no t or p to convert',temperature_qc,     &
!             pressure_qc,n
              r_data=-888888.
            endif
          endif
          varobs(4,n)=r_data
        enddo    ! end do imc=1,meas_count
!       print *,'--- sdng n=',n,nlevs_ob(n),lev_in_ob(n),timeob(n)
!       read in non-sounding obs

      ELSEIF(.NOT.is_sound)THEN
        nlevs_ob(n)=1.
        lev_in_ob(n)=1.
        read(nvol,105)slp_data,slp_qc,                                 &
                      ref_pres_data,ref_pres_qc,                       &
                      height_data,height_qc,                           &
                      temperature_data,temperature_qc,                 &
                      u_met_data,u_met_qc,                             &
                      v_met_data,v_met_qc,                             &
                      rh_data,rh_qc,                                   &
                      psfc_data,psfc_qc,                               &
                      precip_data,precip_qc
 105    FORMAT( 1x,9(f11.3,1x,f11.3,1x))

! Ensemble: add disturbance to sfc obs
!     call srand(n)
!     t_rand =+ (rand(2)-0.5)*5
!     call srand(n+100000)
!     u_rand =+ (rand(2)-0.5)*5
!     call srand(n+200000)
!     v_rand =+ (rand(2)-0.5)*5
!     if(temperature_qc.ge.0..and.temperature_qc.lt.30000.  .and.
!    &   temperature_data .gt. -88880.0 )
!    & temperature_data = temperature_data  + t_rand
!     if((u_met_qc.ge.0..and.u_met_qc.lt.30000.).and.
!    &   (v_met_qc.ge.0..and.v_met_qc.lt.30000.).and.
! make sure at least 1 of the components is .ne.0
!    &   (u_met_data.ne.0..or.v_met_data.ne.0.) .and.
!    &   (u_met_data.gt.-88880.0 .and. v_met_data.gt.-88880.0) )then
!         u_met_data = u_met_data + u_rand
!         v_met_data = v_met_data + v_rand
!      endif
! yliu: Ens test - end

!Lilis

! calculate psfc if slp is there
        if((psfc_qc.lt.0.).and.(slp_qc.ge.0..and.slp_qc.lt.30000.).and.   &
              (temperature_qc.ge.0..and.temperature_qc.lt.30000.).and.    &
              (slp_data.gt.90000.))then
          tbar=temperature_data+0.5*elevation*.0065
          psfc_data=slp_data*exp(-elevation/(rovg*tbar))
          varobs(5,n)=psfc_data
          psfc_qc=0.
        endif

!c *No* **Very rough** estimate of psfc from sfc elevation if UUtah ob and elev>1000m
! estimate psfc from temp and elevation
!   Do not know sfc pressure in model at this point.
!      if((psfc_qc.lt.0.).and.(elevation.gt.1000.).and.
!     1   (temperature_qc.ge.0..and.temperature_qc.lt.30000.)
!     1    .and.(platform(7:16).eq.'SYNOP PRET'))then
        if((psfc_qc.lt.0.).and.                                          &
          (temperature_qc.ge.0..and.temperature_qc.lt.30000.))then
          tbar=temperature_data+0.5*elevation*.0065
          psfc_data=100000.*exp(-elevation/(rovg*tbar))
          varobs(5,n)=psfc_data
          psfc_qc=0.
        endif

        if((psfc_qc.ge.0..and.psfc_qc.lt.30000.).and.(psfc_data.gt.70000.  &
        .and.psfc_data.lt.105000.))then
          varobs(5,n)=psfc_data
        else
          varobs(5,n)=-888888.
        endif

        if(varobs(5,n).ge.0.)varobs(5,n)=varobs(5,n)*1.e-3

!Lilie
!ajb Store temperature for WRF
        if(temperature_qc.ge.0..and.temperature_qc.lt.30000.)then

          if((psfc_qc.ge.0..and.psfc_qc.lt.30000.).and.          &
             (psfc_data.gt.70000. .and.psfc_data.lt.105000.))then

            varobs(3,n) = temperature_data
          else
            varobs(3,n)=-888888.
          endif
        else
          varobs(3,n)=-888888.
        endif

! Store horizontal wind components for WRF
        if((u_met_qc.ge.0..and.u_met_qc.lt.30000.).and.            &
           (v_met_qc.ge.0..and.v_met_qc.lt.30000.).and.            &
! make sure at least 1 of the components is .ne.0
           (u_met_data.ne.0..or.v_met_data.ne.0.))then

! If Earth-relative wind vector, need to rotate it to grid-relative coords
             if(u_met_qc.eq.129. .and. v_met_qc.eq.129.) then
                CALL rotate_vector(longitude,u_met_data,v_met_data,   &
                                   obs_proj,map_proj)
             endif
             varobs(1,n)=u_met_data
             varobs(2,n)=v_met_data
        else
             varobs(1,n)=-888888.
             varobs(2,n)=-888888.
        endif

! jc
! if a ship ob has rh<70%, then throw out

        if(plfo(n).eq.3..and.rh_qc.ge.0..and.rh_data.lt.70.)then
          rh_qc=-888888.
          rh_data=-888888.
        endif
!
        r_data=-888888.
        if(rh_qc.ge.0..and.rh_qc.lt.30000.)then
          if((psfc_qc.ge.0..and.psfc_qc.lt.30000.)                       &
          .and.(temperature_qc.ge.0..and.temperature_qc.lt.30000.))then
!           rh_data=amin1(rh_data,96.) ! yliu: do not allow surface to be saturated
            call rh2r(rh_data,temperature_data,psfc_data*.01,            &
                      r_data,0)            ! yliu, change last arg from 1 to 0
          else
!           print *,'rh, but no t or p',temperature_data,
!    1 psfc_data,n
            r_data=-888888.
          endif
        endif
        varobs(4,n)=r_data
      ELSE
        IF (iprt) THEN
           call wrf_message(" ======  ")
           call wrf_message(" NO Data Found ")
        ENDIF
      ENDIF   !end if(is_sound)
! END OF SFC OBS INPUT SECTION
!======================================================================
!======================================================================
! check if ob time is too early (only applies to beginning)
      IF(RTIMOB.LT.TBACK-TWINDO)then
        IF (iprt) call wrf_message("ob too early")
        n=n-1
        GOTO 110
      ENDIF

! check if this ob is a duplicate
! this check has to be before other checks
      njend=n-1
      if(is_sound)njend=n-meas_count
      do njc=1,njend
! Check that time, lat, lon, and platform all match exactly.
! Platforms 1-4 (surface obs) can match with each other. Otherwise,
!   platforms have to match exactly. 
        if( (timeob(n).eq.timeob(njc)) .and.                     &
            (rio(n).eq.rio(njc))       .and.                     &
            (rjo(n).eq.rjo(njc))       .and.                     &
            (plfo(njc).ne.99.) ) then
!yliu: if two sfc obs are departed less than 1km, consider they are redundant
!              (abs(rio(n)-rio(njc))*dscg.gt.1000.)   &
!         .or. (abs(rjo(n)-rjo(njc))*dscg.gt.1000.)   &
!         .or. (plfo(njc).eq.99.) )goto 801
!yliu end
! If platforms different, and either > 4, jump out
          if( ( (plfo(n).le.4.).and.(plfo(njc).le.4.) ) .or.     &
                (plfo(n).eq.plfo(njc)) ) then

! if not a sounding, and levels are the same then replace first occurrence 
            if((.not.is_sound).and.(rko(njc).eq.rko(n))) then
!             print *,'dup single ob-replace ',n,inest,
!             plfo(n),plfo(njc)
! this is the sfc ob replacement part
              do KN = 1,nndgv
                VAROBS(KN,njc)=VAROBS(KN,n)
              enddo
! don't need to switch these because they're the same
!             RIO(njc)=RIO(n)
!             RJO(njc)=RJO(n)
!             RKO(njc)=RKO(n)
!             TIMEOB(njc)=TIMEOB(n)
!             nlevs_ob(njc)=nlevs_ob(n)
!             lev_in_ob(njc)=lev_in_ob(n)
!             plfo(njc)=plfo(n)
! end sfc ob replacement part

              n=n-1
              goto 100
! It's harder to fix the soundings, since the number of levels may be different
! The easiest thing to do is to just set the first occurrence to all missing, and
!    keep the second occurrence, or vice versa.
! For temp or profiler keep the second, for pilot keep the one with more levs
! This is for a temp or prof sounding, equal to same
!  also if a pilot, but second one has more obs
            elseif( (is_sound).and.(plfo(njc).eq.plfo(n)) .and.            &
                    ( (plfo(njc).eq.5.).or.(plfo(njc).eq.9.).or.           &
                    ( (plfo(njc).eq.6.).and.                               &
                      (nlevs_ob(n).ge.nlevs_ob(njc)) ) ) )then
              IF (iprt) THEN
                write(msg,*) 'duplicate sounding - eliminate first occurrence', &
                                       n,inest,meas_count,nlevs_ob(njc),        &
                                       latitude,longitude,plfo(njc)
                call wrf_message(msg)
              ENDIF
              if(lev_in_ob(njc).ne.1.) then
                IF (iprt) THEN
                  write(msg,*) 'problem ******* - dup sndg ',                   &
                               lev_in_ob(njc),nlevs_ob(njc)
                  call wrf_message(msg)
                ENDIF
              endif
!             n=n-meas_count
! set the first sounding ob to missing
              do njcc=njc,njc+nint(nlevs_ob(njc))-1
                do KN = 1,nndgv
                  VAROBS(KN,njcc)=-888888.
                enddo
                plfo(njcc)=99.
              enddo
              goto 100
!  if a pilot, but first one has more obs
            elseif( (is_sound).and.(plfo(njc).eq.plfo(n)) .and.            &
                    (plfo(njc).eq.6.).and.                                 &
                    (nlevs_ob(n).lt.nlevs_ob(njc)) )then
              IF (iprt) THEN
                write(msg,*)                                               &
                 'duplicate pilot sounding - eliminate second occurrence', &
                                 n,inest,meas_count,nlevs_ob(njc),         &
                                 latitude,longitude,plfo(njc)
                call wrf_message(msg)
              ENDIF
              if(lev_in_ob(njc).ne.1.) then
                IF (iprt) THEN
                  write(msg,*) 'problem ******* - dup sndg ',              &
                           lev_in_ob(njc),nlevs_ob(njc)
                  call wrf_message(msg)
                ENDIF
              endif
              n=n-meas_count

!ajb  Reset timeob for discarded indices.
              do imc = n+1, n+meas_count
                timeob(imc) = 99999.
              enddo
              goto 100
! This is for a single-level satellite upper air ob - replace first
            elseif( (is_sound).and.                                        &
                    (nlevs_ob(njc).eq.1.).and.                             &
                    (nlevs_ob(n).eq.1.).and.                               &
                    (varobs(5,njc).eq.varobs(5,n)).and.                    &
                    (plfo(njc).eq.7.).and.(plfo(n).eq.7.) ) then
              IF (iprt) then
                write(msg,*)                                               &
                'duplicate single lev sat-wind ob - replace first',n,      &
                                 inest,meas_count,varobs(5,n)
                call wrf_message(msg)
              ENDIF
! this is the single ua ob replacement part
              do KN = 1,nndgv
                VAROBS(KN,njc)=VAROBS(KN,n)
              enddo
! don't need to switch these because they're the same
!           RIO(njc)=RIO(n)
!           RJO(njc)=RJO(n)
!           RKO(njc)=RKO(n)
!           TIMEOB(njc)=TIMEOB(n)
!           nlevs_ob(njc)=nlevs_ob(n)
!           lev_in_ob(njc)=lev_in_ob(n)
!           plfo(njc)=plfo(n)
! end single ua ob replacement part
              n=n-1
              goto 100
            else
!             IF (iprt) THEN
!               write(msg,*) 'duplicate location, but no match otherwise',n,njc,  &
!                            plfo(n),varobs(5,n),nlevs_ob(n),lev_in_ob(n),        &
!                            plfo(njc),varobs(5,njc),nlevs_ob(njc),lev_in_ob(njc)
!               call wrf_message(msg)
!             ENDIF
            endif
          endif
        endif
! end of njc do loop
      enddo

! check if ob is a sams ob that came in via UUtah - discard
      if( plfo(n).eq.4..and.(platform(7:16).eq.'SYNOP PRET').and.          &
          (id(7:15).eq.'METNET= 3') )then
!       print *,'elim metnet=3',latitude,longitude,rtimob
        n=n-1
        goto 100
      endif

! check if ob is in the domain
      if( (ri.lt.2.).or.(ri.gt.real(e_we-1)).or.(rj.lt.2.).or.         &
          (rj.gt.real(e_sn-1)) ) then

          n=n-meas_count
!ajb  Reset timeob for discarded indices.
          do imc = n+1, n+meas_count
            timeob(imc) = 99999.
          enddo
          goto 100
      endif

      IF(TIMEOB(N).LT.fdob%RTLAST) THEN
        IF (iprt) THEN
          call wrf_message("2 OBS ARE NOT IN CHRONOLOGICAL ORDER")
          call wrf_message("NEW YEAR?")
          write(msg,*) 'timeob,rtlast,n=',timeob(n),fdob%rtlast,n
          call wrf_message(msg)
        ENDIF
        call wrf_error_fatal ( 'wrf_fddaobs_in: in4dob STOP 111' )
      ELSE
        fdob%RTLAST=TIMEOB(N)
      ENDIF
! Save obs and model latitude and longitude for printout
      CALL collect_obs_info(newpass,inest,n,latitude,longitude,              &
                         nlast,nprev,niobf,id,stnid_prt,                     &
                         rio,rjo,prt_max,prt_freq,xlat,xlong,                &
                         obs_prt,lat_prt,lon_prt,mlat_prt,mlon_prt,          &
                         e_we,e_sn,ims,ime,jms,jme,its,ite,jts,jte)
      GOTO 100
  111 CONTINUE
!**********************************************************************
!       --------------   END BIG 100 LOOP OVER N  --------------
!**********************************************************************

      if (iprt) then
        write(msg,5403) NVOL,XTIME
        call wrf_message(msg)
      endif
      IEOF(inest)=1

      close(NVOLA+INEST-1)
      IF (iprt) then
         write(msg,*) 'closed fdda file for inest=',inest,nsta
         call wrf_message(msg)
      ENDIF

! AJB note: Go back and check for more files. (Multi-file implementation)
  goto 1001

120 CONTINUE
! THE OBSERVATION ARRAYS ARE FULL AND THE MOST RECENTLY
! ACQUIRED OBS STILL HAS TIMEOB .LE. TFORWD.  SO START
! DECREASING THE SIZE OF THE WINDOW
! get here if too many obs
  IF (iprt) THEN
    write(msg,121) N,NIOBF
    call wrf_message(msg)
  ENDIF
  call wrf_error_fatal ( 'wrf_fddaobs_in: in4dob STOP 122' )

130 CONTINUE
! READ CYCLE IS COMPLETED. DETERMINE THE NUMBER OF OBS IN
! THE CURRENT WINDOW
!
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
! BUT FIRST, WHEN KTAU.EQ.0 (OR IN GENERAL, KTAUR), DISCARD THE
! "OLD" OBS FIRST...

! Get here if at end of file, or if obs time is beyond what we need right now.
! On startup, we report the index of the last obs read.
! For restarts, we need to remove any old obs and then repack obs list.
  IF(KTAU.EQ.KTAUR)THEN
    NSTA=0
    keep_obs : DO N=1,NIOBF
! try to keep all obs, but just don't use yet
!  (don't want to throw away last obs read in - especially if
!  its a sounding, in which case it looks like many obs)
      IF(TIMEOB(N).GT.9.e4) EXIT keep_obs
      if(timeob(n).gt.tforwd) then
        if(iprt) then
           write(msg,950) inest
           call wrf_message(msg)
           write(msg,951) n,timeob(n),tforwd
           call wrf_message(msg)
        endif
 950    FORMAT('Saving index of first ob after end of current time window ', &
               'for nest = ', i3,':')
 951    FORMAT('  ob index = ',i8,',   time of ob = ',f8.4,                  &
               ' hrs,   end of time window = ',f8.4,' hrs')
      endif
      NSTA=N
    ENDDO keep_obs

    NDUM=0
! make time=99999. if ob is too old
!   print *,'tback,nsta=',tback,nsta
    old_obs : DO N=1,NSTA+1
      IF((TIMEOB(N)-TBACK).LT.0)THEN
        TIMEOB(N)=99999.
      ENDIF
!     print *,'n,ndum,timeob=',n,ndum,timeob(n)
      IF(TIMEOB(N).LT.9.E4) EXIT old_obs
      NDUM=N
    ENDDO old_obs

! REMOVE OLD OBS DENOTED BY 99999. AT THE FRONT OF TIMEOB ARRAY
    IF (iprt .and. ktaur > 0) THEN
      write(msg,fmt='(a,i5,a)') 'OBS NUDGING: Upon restart, skipped over ',ndum,   &
                ' obs that are now too old for the current obs window.'
      call wrf_message(msg)
    ENDIF

    NDUM=ABS(NDUM)
    NMOVE=NIOBF-NDUM
    IF( NMOVE.GT.0 .AND. NDUM.NE.0) THEN
      DO N=1,NMOVE
        do KN = 1,nndgv
          VAROBS(KN,N)=VAROBS(KN,N+NDUM)
        enddo
        RJO(N)=RJO(N+NDUM)
        RIO(N)=RIO(N+NDUM)
        RKO(N)=RKO(N+NDUM)
        TIMEOB(N)=TIMEOB(N+NDUM)
        nlevs_ob(n)=nlevs_ob(n+ndum)
        lev_in_ob(n)=lev_in_ob(n+ndum)
        plfo(n)=plfo(n+ndum)
      ENDDO
    ENDIF
! moved obs up. now fill remaining space with 99999.
    NOPEN=NMOVE+1
    IF(NOPEN.LE.NIOBF) THEN
      DO N=NOPEN,NIOBF
        do KN = 1,nndgv
          VAROBS(KN,N)=99999.
        enddo
        RIO(N)=99999.
        RJO(N)=99999.
        RKO(N)=99999.
        TIMEOB(N)=99999.
      ENDDO
    ENDIF
  ENDIF
!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
  NSTA=0
! print *,'nsta at restart setting is ',nsta
! recalculate nsta after moving things around
  recalc : DO N=1,NIOBF
! try to save all obs - don't throw away latest read in
    IF(TIMEOB(N).GT.9.e4) EXIT recalc
    NSTA=N
!   nsta=n-1         ! yliu test
  ENDDO recalc

! Find the number of stations that are actually within the time window.
  nstaw = nvals_le_limit(nsta, timeob, tforwd)

  IF (iprt) then
      write(msg,160) KTAU,XTIME,NSTAW
      call wrf_message(msg)
  ENDIF
  IF(KTAU.EQ.KTAUR)THEN
    IF(nudge_opt.EQ.1)THEN
      TWDOP=TWINDO*60.
      IF (iprt) THEN
        write(msg,1449) INEST,RINXY,RINSIG,TWDOP
        call wrf_message(msg)
        IF(ISWIND.EQ.1) then
          write(msg,1450) GIV
          call wrf_message(msg)
        ELSE
          write(msg,1455) INEST
          call wrf_message("")
          call wrf_message(msg)
          call wrf_message("")
        ENDIF
        IF(ISTEMP.EQ.1) then
          write(msg,1451) GIT
          call wrf_message(msg)
        ELSE
          write(msg,1456) INEST
          call wrf_message("")
          call wrf_message(msg)
        ENDIF
        IF(ISMOIS.EQ.1) then
          call wrf_message("")
          write(msg,1452) GIQ
          call wrf_message(msg)
        ELSE
          write(msg,1457) INEST
          call wrf_message("")
          call wrf_message(msg)
          call wrf_message("")
        ENDIF
      ENDIF
    ENDIF
  ENDIF
  IF(KTAU.EQ.KTAUR)THEN
    IF(fdob%IWTSIG.NE.1)THEN
      IF (iprt) THEN
        write(msg,555)
        call wrf_message(msg)
        write(msg,556) fdob%RINFMN*RINXY,fdob%RINFMX*RINXY,fdob%PFREE*10.
        call wrf_message(msg)
      ENDIF
      IF(fdob%RINFMN.GT.fdob%RINFMX) then
         call wrf_error_fatal ( 'wrf_fddaobs_in: in4dob STOP 556' )
      ENDIF
! IS MINIMUM GREATER THAN MAXIMUM?

      IF (iprt) then
        write(msg,557) fdob%DPSMX*10.,fdob%DCON
        call wrf_message(msg)
      ENDIF
      IF(fdob%DPSMX.GT.10.) then
         call wrf_error_fatal ( 'wrf_fddaobs_in: in4dob STOP 557' )
      ENDIF
    ENDIF
  ENDIF
 
  IF(KTAU.EQ.KTAUR)THEN
    IF (iprt) then
      write(msg,601) INEST,IONF
      call wrf_message(msg)
      call wrf_message("")
    ENDIF
  ENDIF
  fdob%NSTAT=NSTA
  fdob%NSTAW=NSTAW

555   FORMAT(1X,'   ABOVE THE SURFACE LAYER, OBS NUDGING IS PERFORMED',  &
      ' ON PRESSURE LEVELS,')
556   FORMAT(1X,'   WHERE RINXY VARIES LINEARLY FROM ',E11.3,' KM AT',   &
      ' THE SURFACE TO ',E11.3,' KM AT ',F7.2,' MB AND ABOVE')
557   FORMAT(1X,'   IN THE SURFACE LAYER, WXY IS A FUNCTION OF ',        &
      'DPSMX = ',F7.2,' MB WITH DCON = ',E11.3,                          &
      ' - SEE SUBROUTINE NUDOB')
601   FORMAT('FOR EFFICIENCY, THE OBS NUDGING FREQUENCY ',               &
        'FOR MESH #',I2,' IS ',1I2,' CGM TIMESTEPS ')
121   FORMAT('  WARNING: NOBS  = ',I4,' IS GREATER THAN NIOBF = ',       &
      I4,': INCREASE PARAMETER NIOBF')
5403  FORMAT(1H0,'-------------EOF REACHED FOR NVOL = ',I3,              &
       ' AND XTIME = ',F10.2,'-------------------')
160   FORMAT('****** CALL IN4DOB AT KTAU = ',I5,' AND XTIME = ',         &
      F10.2,':  NSTA = ',I7,' ******')
1449  FORMAT('*****NUDGING INDIVIDUAL OBS ON MESH #',I2,                 &
       ' WITH RINXY = ',                                                 &
      E11.3,' KM, RINSIG = ',E11.3,' AND TWINDO (HALF-PERIOD) = ',       &
      E11.3,' MIN')
1450  FORMAT(1X,'NUDGING IND. OBS WINDS WITH GIV = ',E11.3)
1451  FORMAT(1X,'NUDGING IND. OBS TEMPERATURE WITH GIT = ',E11.3)
1452  FORMAT(1X,'NUDGING IND. OBS MOISTURE WITH GIQ = ',E11.3)
1455  FORMAT(1X,'*** OBS WIND NUDGING FOR MESH ',I2,' IS TURNED OFF!!')
1456  FORMAT(1X,'*** OBS TEMPERATURE NUDGING FOR MESH ',I2,' IS TURNED OFF!!')
1457  FORMAT(1X,'*** OBS MOISTURE NUDGING FOR MESH ',I2,' IS TURNED OFF!!')

  RETURN
  END SUBROUTINE in4dob


  SUBROUTINE julgmt(mdate,julgmtn,timanl,julday,gmt,ind)
! CONVERT MDATE YYMMDDHH TO JULGMT (JULIAN DAY * 100. +GMT)
! AND TO TIMANL (TIME IN MINUTES WITH RESPECT TO MODEL TIME)
! IF IND=0  INPUT MDATE, OUTPUT JULGMTN AND TIMANL
! IF IND=1  INPUT TIMANL, OUTPUT JULGMTN
! IF IND=2  INPUT JULGMTN, OUTPUT TIMANL
      INTEGER, intent(in) :: MDATE
      REAL, intent(out) :: JULGMTN
      REAL, intent(out) :: TIMANL
      INTEGER, intent(in) :: JULDAY
      REAL, intent(in) :: GMT
      INTEGER, intent(in) :: IND 

!***  DECLARATIONS FOR IMPLICIT NONE                                    
      real :: MO(12), rjulanl, houranl, rhr

      integer :: iyr, idate1, imo, idy, ihr, my1, my2, my3, ileap
      integer :: juldayn, juldanl, idymax, mm
      
      
      IF(IND.EQ.2)GOTO 150
      IYR=INT(MDATE/1000000.+0.001)
      IDATE1=MDATE-IYR*1000000
      IMO=INT(IDATE1/10000.+0.001)
      IDY=INT((IDATE1-IMO*10000.)/100.+0.001)
      IHR=IDATE1-IMO*10000-IDY*100
      MO(1)=31
      MO(2)=28
! IS THE YEAR A LEAP YEAR? (IN THIS CENTURY)
      IYR=IYR+1900
      MY1=MOD(IYR,4)
      MY2=MOD(IYR,100)
      MY3=MOD(IYR,400)
      ILEAP=0
! jc
!      IF(MY1.EQ.0.AND.MY2.NE.0.OR.MY3.EQ.0)THEN
      IF(MY1.EQ.0)THEN
        ILEAP=1
        MO(2)=29
      ENDIF
      IF(IND.EQ.1)GOTO 200
      MO(3)=31
      MO(4)=30
      MO(5)=31
      MO(6)=30
      MO(7)=31
      MO(8)=31
      MO(9)=30
      MO(10)=31
      MO(11)=30
      MO(12)=31
      JULDAYN=0
      DO 100 MM=1,IMO-1
        JULDAYN=JULDAYN+MO(MM)
 100     CONTINUE

      IF(IHR.GE.24)THEN
        IDY=IDY+1
        IHR=IHR-24
      ENDIF
      JULGMTN=(JULDAYN+IDY)*100.+IHR
! CONVERT JULGMT TO TIMANL WRT MODEL TIME IN MINUTES (XTIME)
 150   CONTINUE
      JULDANL=INT(JULGMTN/100.+0.000001)
      RJULANL=FLOAT(JULDANL)*100.
      HOURANL=JULGMTN-RJULANL
      TIMANL=(FLOAT(JULDANL-JULDAY)*24.-GMT+HOURANL)*60.
      RETURN
 200   CONTINUE
      RHR=GMT+TIMANL/60.+0.000001
      IDY=JULDAY
      IDYMAX=365+ILEAP
 300   IF(RHR.GE.24.0)THEN
        RHR=RHR-24.0
        IDY=IDY+1
        GOTO 300
      ENDIF
      IF(IDY.GT.IDYMAX)IDY=IDY-IDYMAX
      JULGMTN=FLOAT(IDY)*100.+RHR
      RETURN
  END SUBROUTINE julgmt


  SUBROUTINE rh2r(rh,t,p,r,iice) 2
 
! convert rh to r
! if iice=1, use saturation with respect to ice
! rh is 0-100.
! r is g/g
! t is K
! p is mb
!
      REAL, intent(in)  :: rh
      REAL, intent(in)  :: t
      REAL, intent(in)  :: p
      REAL, intent(out) :: r
      INTEGER, intent(in)  :: iice

!***  DECLARATIONS FOR IMPLICIT NONE                                    
      real eps, e0, eslcon1, eslcon2, esicon1, esicon2, t0, rh1
      real esat, rsat

      eps=0.62197
      e0=6.1078
      eslcon1=17.2693882
      eslcon2=35.86
      esicon1=21.8745584
      esicon2=7.66
      t0=260.
 
!     print *,'rh2r input=',rh,t,p
      rh1=rh*.01
 
      if(iice.eq.1.and.t.le.t0)then
        esat=e0*exp(esicon1*(t-273.16)/(t-esicon2))
      else
        esat=e0*exp(eslcon1*(t-273.16)/(t-eslcon2))
      endif
      rsat=eps*esat/(p-esat)
!     print *,'rsat,esat=',rsat,esat
      r=rh1*rsat
 
!      print *,'rh2r rh,t,p,r=',rh1,t,p,r
 
      return
  END SUBROUTINE rh2r


  SUBROUTINE rh2rb(rh,t,p,r,iice),2
 
! convert rh to r
! if iice=1, use daturation with respect to ice
! rh is 0-100.
! r is g/g
! t is K
! p is mb
 
      REAL, intent(in)  :: rh
      REAL, intent(in)  :: t
      REAL, intent(in)  :: p
      REAL, intent(out) :: r
      INTEGER, intent(in)  :: iice

!***  DECLARATIONS FOR IMPLICIT NONE                                    
      real eps, e0, eslcon1, eslcon2, esicon1, esicon2, t0, rh1
      real esat, rsat
      character(len=200) :: msg       ! Argument to wrf_message

      eps=0.622
      e0=6.112
      eslcon1=17.67
      eslcon2=29.65
      esicon1=22.514
      esicon2=6.15e3
      t0=273.15
 
      write(msg,*) 'rh2r input=',rh,t,p
      call wrf_message(msg)
      rh1=rh*.01
 
      if(iice.eq.1.and.t.le.t0)then
        esat=e0*exp(esicon1-esicon2/t)
      else
        esat=e0*exp(eslcon1*(t-t0)/(t-eslcon2))
      endif
      rsat=eps*esat/(p-esat)
!     print *,'rsat,esat=',rsat,esat
      r=rh1*eps*rsat/(eps+rsat*(1.-rh1))
 
      write(msg,*) 'rh2r rh,t,p,r=',rh1,t,p,r
      call wrf_message(msg)
      rh1=rh*.01
 
      return
END SUBROUTINE rh2rb


  SUBROUTINE set_projection (obs_proj, map_proj, cen_lat, cen_lon,     & 1,7
                             true_lat1, true_lat2, stand_lon,          &
                             known_lat, known_lon,                     &
                             e_we, e_sn, dxm, dym )

  USE module_llxy

!*************************************************************************
! Purpose: Set map projection information which will be used to map the
!          observation (lat,lon) location to its corresponding (x,y)
!          location on the WRF (coarse) grid. using the selected map
!          projection (e.g., Lambert, Mercator, Polar Stereo, etc).
!*************************************************************************

      IMPLICIT NONE

  TYPE(PROJ_INFO), intent(out)  :: obs_proj   ! structure for obs projection info.
  INTEGER, intent(in) :: map_proj             ! map projection index
  REAL, intent(in) :: cen_lat                 ! center latitude for map projection
  REAL, intent(in) :: cen_lon                 ! center longiture for map projection
  REAL, intent(in) :: true_lat1               ! truelat1 for map projection
  REAL, intent(in) :: true_lat2               ! truelat2 for map projection
  REAL, intent(in) :: stand_lon               ! standard longitude for map projection
  INTEGER, intent(in) :: e_we                 ! max grid index in south-north coordinate
  INTEGER, intent(in) :: e_sn                 ! max grid index in west-east   coordinate
  REAL, intent(in) :: known_lat               ! latitude  of domain origin point (i,j)=(1,1) 
  REAL, intent(in) :: known_lon               ! longigude of domain origin point (i,j)=(1,1)
  REAL, intent(in) :: dxm                     ! grid size in x (meters)
  REAL, intent(in) :: dym                     ! grid size in y (meters)

! Set up map transformation structure
      CALL map_init(obs_proj)

      ! Mercator
      IF (map_proj == PROJ_MERC) THEN
         CALL map_set(PROJ_MERC, obs_proj,                                &
                      truelat1 = true_lat1,                               &
                      lat1     = known_lat,                               &
                      lon1     = known_lon,                               &
                      knowni   = 1.,                                      &
                      knownj   = 1.,                                      &
                      dx       = dxm)

      ! Lambert conformal
      ELSE IF (map_proj == PROJ_LC) THEN
      CALL map_set(PROJ_LC, obs_proj,                                     &
                      truelat1 = true_lat1,                               &
                      truelat2 = true_lat2,                               &
                      stdlon   = stand_lon,                               &
                      lat1     = known_lat,                               &
                      lon1     = known_lon,                               &
                      knowni   = 1.,                                      &
                      knownj   = 1.,                                      &
                      dx       = dxm)

      ! Polar stereographic
      ELSE IF (map_proj == PROJ_PS) THEN
         CALL map_set(PROJ_PS, obs_proj,                                  &
                      truelat1 = true_lat1,                               &
                      stdlon   = stand_lon,                               &
                      lat1     = known_lat,                               &
                      lon1     = known_lon,                               &
                      knowni   = 1.,                                      &
                      knownj   = 1.,                                      &
                      dx       = dxm)
      ! Cassini (global ARW)
      ELSE IF (map_proj == PROJ_CASSINI) THEN
         CALL map_set(PROJ_CASSINI, obs_proj,                             &
                      latinc   = dym*360.0/(2.0*EARTH_RADIUS_M*PI),       &
                      loninc   = dxm*360.0/(2.0*EARTH_RADIUS_M*PI),       &
                      lat1     = known_lat,                               &
                      lon1     = known_lon,                               &
! We still need to get POLE_LAT and POLE_LON metadata variables before
!   this will work for rotated poles.
                      lat0     = 90.0,                                    &
                      lon0     = 0.0,                                     &
                      knowni   = 1.,                                      &
                      knownj   = 1.,                                      &
                      stdlon   = stand_lon)

      ! Rotated latitude-longitude
      ELSE IF (map_proj == PROJ_ROTLL) THEN
         CALL map_set(PROJ_ROTLL, obs_proj,                               &
! I have no idea how this should work for NMM nested domains
                      ixdim    = e_we-1,                                  &
                      jydim    = e_sn-1,                                  &
                      phi      = real(e_sn-2)*dym/2.0,                    &
                      lambda   = real(e_we-2)*dxm,                        &
                      lat1     = cen_lat,                                 &
                      lon1     = cen_lon,                                 &
                      latinc   = dym,                                     &
                      loninc   = dxm,                                     &
                      stagger  = HH)

      END IF

  END SUBROUTINE set_projection


  SUBROUTINE fmt_date(idate,odate)                                             !obsnypatch 1

!*************************************************************************
! Purpose: Re-format a character date string from YYYYMMDDHHmmss form
!          to YYYY-MM-DD_HH:mm:ss form.
! INPUT:
!     IDATE - Date string as YYYYMMDDHHmmss
! OUTPUT:
!     ODATE - Date string as YYYY-MM-DD_HH:mm:ss
!*************************************************************************

      IMPLICIT NONE

      CHARACTER*14, intent(in)  :: idate        ! input  date string
      CHARACTER*19, intent(out) :: odate        ! output date string

      odate(1:19) = "0000-00-00_00:00:00"
      odate(1:4)   = idate(1:4)                 ! Year
      odate(6:7)   = idate(5:6)                 ! Month
      odate(9:10)  = idate(7:8)                 ! Day
      odate(12:13) = idate(9:10)                ! Hours
      odate(15:16) = idate(11:12)               ! Minutes
      odate(18:19) = idate(13:14)               ! Seconds

      RETURN
  END SUBROUTINE fmt_date


  INTEGER FUNCTION nvals_le_limit(isize, values, limit) 1
!------------------------------------------------------------------------------
! PURPOSE: Return the number of values in a (real) non-decreasing array that
!          are less than or equal to the specified upper limit.
! NOTE: It is important that the array is non-decreasing!
!      
!------------------------------------------------------------------------------
  IMPLICIT NONE

  INTEGER, INTENT(IN) :: isize           ! Size of input array
  REAL,    INTENT(IN) :: values(isize)   ! Input array of reals
  REAL,    INTENT(IN) :: limit           ! Upper limit

! Local variables
  integer :: n

! Search the array from largest to smallest values.
   find_nvals: DO n = isize, 1, -1
                 if(values(n).le.limit) EXIT find_nvals
               ENDDO find_nvals
  nvals_le_limit = n

  RETURN
  END FUNCTION nvals_le_limit


  SUBROUTINE collect_obs_info(newpass,inest,n,latitude,longitude,             & 1,4
                              nlast,nprev,niobf,station_id,stnid,             &
                              rio,rjo,prt_max,prt_freq,xlat,xlong,            &
                              obs, lat,lon, mlat,mlon,                        &
                              e_we,e_sn,ims,ime,jms,jme,its,ite,jts,jte)
!*************************************************************************
! Purpose: Collect the obs index, obs latitude, obs longitude, obs station
!          id, and model latitude and longitude values for print
!          diagnostics. Note that THIS SUBROUTINE IS CALLED INTERATIVELY
!          FROM IN4DOB, WITHIN THE OBS READ LOOP that reads new obser-
!          vations needed for the new time window. Flag newpass is true
!          the first time collect_obs_info is called from the read-loop
!          for a new time window. So for each pass of IN4DOB, newpass is
!          true the first time IN4DOB calls collec_obs_info.

!          OBS (soundings) contain multiple obs levels. So on each sub-
!          sequent call of collect_obs_info for a specific pass of IN4DOB,
!          n will jump by the number of levels in the sounding.
!
!          Here, nlast refers to the index of the last valid-time obs
!          that was read in during the last pass of IN4DOB (after the old
!          obs were removed). This way we can properly start storing
!          obs information for the new obs that are being read on this
!          pass of IN4DOB, beginning with the first newly read obs for
!          this pass of IN4DOB.
!
!          Note that nprev is needed to properly handle soundings. On
!          each pass, n is stored into nprev, and on each subsequent
!          pass of collect_obs_info, a loop is performed from nprev+1 to n.
!*************************************************************************

  IMPLICIT NONE

  LOGICAL, intent(inout) :: newpass        ! New pass flag
  INTEGER, intent(in)    :: inest          ! nest index
  INTEGER, intent(in)    :: n              ! Observation index
  REAL,    intent(in)    :: latitude       ! Latitude of obs
  REAL,    intent(in)    :: longitude      ! Latitude of obs
  INTEGER, intent(in)    :: nlast          ! Last obs of valid obs, prev window
  INTEGER, intent(inout) :: nprev          ! Previous obs in new window read seq
  INTEGER, intent(in)    :: niobf          ! Maximum number of observations
  CHARACTER*15, intent(in) :: station_id   ! First 15 chars of station id for obs n
  INTEGER, intent(in)    :: prt_max        ! Max no. of obs for diagnostic printout
  INTEGER, intent(inout) :: stnid(40,prt_max) ! Station ids for diagnostic printout
  REAL,    intent(in)    :: rio(niobf)     ! West-east coord (non-stagger)
  REAL,    intent(in)    :: rjo(niobf)     ! South-north coord (non-stagger)
  INTEGER, intent(in)    :: prt_freq       ! Frequency for diagnostic printout
  REAL, DIMENSION( ims:ime, jms:jme ),                                   &
           intent(in )   :: xlat, xlong    ! Lat/lon on mass-pt grid
  INTEGER, intent(inout) :: obs(prt_max)   ! Obs index for printout
  REAL,    intent(inout) :: lat(prt_max)   ! Obs latitude for printout
  REAL,    intent(inout) :: lon(prt_max)   ! Obs longitude for printout
  REAL,    intent(inout) :: mlat(prt_max)  ! Model latitude at (rio,rjo) for printout
  REAL,    intent(inout) :: mlon(prt_max)  ! Model longitude at (rio,rjo) for printout
  INTEGER, intent(in)    :: e_we           ! Max grid index in south-north
  INTEGER, intent(in)    :: e_sn           ! Max grid index in west-east
  INTEGER, intent(in)    :: ims            ! Grid mem start (west-east)
  INTEGER, intent(in)    :: ime            ! Grid mem end   (west-east)
  INTEGER, intent(in)    :: jms            ! Grid mem start (south-north)
  INTEGER, intent(in)    :: jme            ! Grid mem end   (south-north)
  INTEGER, intent(in)    :: its            ! Grid tile start (west-east)
  INTEGER, intent(in)    :: ite            ! Grid tile end   (west-east)
  INTEGER, intent(in)    :: jts            ! Grid tile start (south-north)
  INTEGER, intent(in)    :: jte            ! Grid tile end   (south-north)

! Local variables
  integer i                       ! Loop counter over station id character
  integer nn                      ! Loop counter over obs index
  integer ndx,ndxp                ! Index into printout arrays (ndx and prev ndx)
  real    :: ri, rj               ! Mass-pt coord of obs
  integer :: ril, rjl             ! Mass-pt integer coord immed sw of obs
  integer :: iend, jend           ! Upper i, j index for interpolation
  real    :: dxob, dyob           ! Grid fractions for interp
  logical :: llsave               ! Save lat/lon values if true
  character(len=200) :: msg       ! Argument to wrf_message

  if(newpass) then
    newpass = .false.
    nprev = nlast       ! Reset in case old obs have been discarded from prev window
  endif

! Start iteration only if we have not yet stored prt_max number of obs for printing.
! Note: The loop below could represent multiple levels in a sounding, so we
!       go ahead and start the loop if the beginning index (ndx) is prt_max or
!       less, and then exit the loop if ndx exceeds prt_max.
    if(prt_freq.gt.0) then
       ndx  = (n-nlast-1)/prt_freq + 1
       ndxp = (nprev-nlast-1)/prt_freq + 1
    else
       write(msg,*) 'STOP! OBS NAMELIST INPUT obs_prt_freq MUST BE GREATER THAN ZERO.'
       call wrf_message(msg)
       write(msg,*) 'THE NAMELIST VALUE IS',prt_freq,' FOR NEST ',inest
       call wrf_message(msg)
       call wrf_error_fatal ( 'wrf_fddaobs_in: in4dob STOP' )
    endif

!   write(6,'5(a,i5),a,a15') 'n = ',n,'  nlast = ',nlast,'  ndx = ',ndx,  &
!                            '  nprev = ',nprev,'  ndxp = ',ndxp,         &
!                            '  station id = ',station_id

    if(ndxp .lt. prt_max) then

   MODCHK : do nn = nprev+1, n
        llsave = .false.

!       if( mod(nn-1,prt_freq) .eq. 0 ) then
        if( mod(nn-nlast-1,prt_freq) .eq. 0 ) then
           ndx = (nn-nlast-1)/prt_freq + 1
           if(ndx.gt.prt_max) EXIT MODCHK       ! Limit printout to prt_max entries
           llsave = .true.
        endif
        if(llsave) then

! Collect obs index and latitude and longitude.
          obs(ndx) = nn
          lat(ndx) = latitude
          lon(ndx) = longitude

! Collect first 15 chars of obs station id (in integer format).
          do i = 1,15
            stnid(i,ndx) = ichar(station_id(i:i))
          enddo

! Compute and collect the model latitude and longitude at the obs point.
          CALL get_model_latlon(nn,niobf,rio,rjo,xlat,xlong,e_we,e_sn,    &
                                ims,ime,jms,jme,its,ite,jts,jte,          &
                                mlat(ndx),mlon(ndx))
        endif  !end if(llsave)
      enddo MODCHK

    endif  !end if(ndx .le. prt_max)

! Save index of previous obs in read loop.
    nprev = n

  END SUBROUTINE collect_obs_info


  SUBROUTINE get_model_latlon(n,niobf,rio,rjo,xlat,xlong,e_we,e_sn,   & 1
                              ims,ime,jms,jme,its,ite,jts,jte,        &
                              mlat,mlon)
!*************************************************************************
! Purpose: Use bilinear interpolation to compute the model latitude and 
!          longitude at the observation point.
!*************************************************************************

  IMPLICIT NONE

  INTEGER, intent(in)    :: n              ! Observation index
  INTEGER, intent(in)    :: niobf          ! Maximum number of observations
  REAL,    intent(in)    :: rio(niobf)     ! West-east coord (non-stagger)
  REAL,    intent(in)    :: rjo(niobf)     ! South-north coord (non-stagger)
  REAL, DIMENSION( ims:ime, jms:jme ),                                   &
           intent(in )   :: xlat, xlong    ! Lat/lon on mass-pt grid
  INTEGER, intent(in)    :: e_we           ! Max grid index in south-north
  INTEGER, intent(in)    :: e_sn           ! Max grid index in west-east
  INTEGER, intent(in)    :: ims            ! Grid mem start (west-east)
  INTEGER, intent(in)    :: ime            ! Grid mem end   (west-east)
  INTEGER, intent(in)    :: jms            ! Grid mem start (south-north)
  INTEGER, intent(in)    :: jme            ! Grid mem end   (south-north)
  INTEGER, intent(in)    :: its            ! Grid tile start (west-east)
  INTEGER, intent(in)    :: ite            ! Grid tile end   (west-east)
  INTEGER, intent(in)    :: jts            ! Grid tile start (south-north)
  INTEGER, intent(in)    :: jte            ! Grid tile end   (south-north)
  REAL,    intent(out)   :: mlat           ! Model latitude at obs point
  REAL,    intent(out)   :: mlon           ! Model longitude at obs point

! Local variables
  integer ndx                     ! Index into save arrays
  real    :: ri, rj               ! Mass-pt coord of obs 
  integer :: ril, rjl             ! Mass-pt integer coord immed sw of obs 
  integer :: iend, jend           ! Upper i, j index for interpolation
  real    :: dxob, dyob           ! Grid fractions for interp

! Compute model latitude and longitude if point on tile.
  ri  = rio(n) - .5            ! mass-pt west-east obs grid coord
  rj  = rjo(n) - .5            ! mass-pt south-north obs grid coord
  ril = int(ri)
  rjl = int(rj)
  dxob = ri - float(ril)
  dyob = rj - float(rjl)
  iend = min(ite+1,e_we-2)
  jend = min(jte+1,e_sn-2)
  mlat = -999
  mlon = -999

  if(ri.ge.its .and. ri.lt.iend .and. rj.ge.jts .and. rj.lt.jend) then

! bilinear interpolation
     mlat = ((1.-dyob)*((1.-dxob)*xlat(ril,rjl)+             &
            dxob*xlat(ril+1,rjl)                             &
            )+dyob*((1.-dxob)*xlat(ril,rjl+1)+               &
            dxob*xlat(ril+1,rjl+1)))

     mlon = ((1.-dyob)*((1.-dxob)*xlong(ril,rjl)+            &
            dxob*xlong(ril+1,rjl)                            &
            )+dyob*((1.-dxob)*xlong(ril,rjl+1)+              &
            dxob*xlong(ril+1,rjl+1)))
  endif

  END SUBROUTINE get_model_latlon


  SUBROUTINE rotate_vector(lon,u,v,obs_proj,map_proj) 2,1

  USE module_llxy

!*************************************************************************
! Purpose: Rotate a single Earth-relative wind vector to a grid-relative
!          wind vector.
!*************************************************************************

  IMPLICIT NONE

  REAL,           intent(in)    :: lon        ! Longitude (deg)
  REAL,           intent(inout) :: u          ! U-component of wind vector
  REAL,           intent(inout) :: v          ! V-component of wind vector
  TYPE(PROJ_INFO),intent(in)    :: obs_proj   ! Structure for obs projection
  INTEGER,        intent(in)    :: map_proj   ! Map projection index

! Local variables
  real diff, alpha
  double precision udbl, vdbl

! Only rotate winds for Lambert conformal or polar stereographic
  if (map_proj == PROJ_LC .or. map_proj == PROJ_PS) then

     diff = obs_proj%stdlon - lon
     if (diff > 180.) then
        diff = diff - 360.
     else if (diff < -180.) then
        diff = diff + 360.
     end if

! Calculate the rotation angle, alpha, in radians
     if (map_proj == PROJ_LC) then
        alpha = diff * obs_proj%cone * rad_per_deg * obs_proj%hemi
     else
        alpha = diff * rad_per_deg * obs_proj%hemi
     end if

     udbl = v*sin(alpha) + u*cos(alpha)
     vdbl = v*cos(alpha) - u*sin(alpha)
     u = udbl
     v = vdbl

  endif
  END SUBROUTINE rotate_vector

#endif
!-----------------------------------------------------------------------
! End subroutines for in4dob
!-----------------------------------------------------------------------