!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Module: proc_point_module
!
! Purpose: This module provides routines that produce a value for a model grid
!    point in two ways. If the field for which a value is being calculated is
!    a continuous field, this module provided functionality to interpolate 
!    from the source array to the specified point. If the field is a categorical
!    field, this module provided functionality to accumulate the values of all 
!    source points whose nearest model gridpoint is the specified point.
!    Routines are also provided that help the caller determine an optimized 
!    order in which to process the model grid points.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
module proc_point_module

   ! Modules
   use bitarray_module
   use hash_module
   use misc_definitions_module
   use module_debug
   use source_data_module
 
   ! Information about which tile is in memory
   integer :: src_min_x, src_max_x, src_min_y, src_max_y, src_min_z, src_max_z, src_npts_bdr
   integer :: src_level
   character (len=128) :: src_fieldname
   character (len=256) :: src_fname
 
   ! Source tiles
   real, pointer, dimension(:,:,:) :: src_array
 
   ! Hash to track which tiles we have already processed
   type (hashtable) :: h_table
 
   contains
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: proc_point_init 
   !
   ! Purpose: Initialize the module.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   subroutine proc_point_init()
 
      implicit none
  
      ! Initialize module variables
      src_min_x = INVALID
      src_min_y = INVALID
      src_min_z = INVALID
      src_max_x = INVALID
      src_max_y = INVALID
      src_max_z = INVALID
      src_fieldname = ' '
      src_fname = ' '
      nullify(src_array)
  
      call hash_init(h_table)
 
   end subroutine proc_point_init 
 
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: proc_point_shutdown 
   !
   ! Purpose: Do any cleanup work.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   subroutine proc_point_shutdown()
 
      implicit none
  
      ! Effectively reset the hash table that tracks which tiles have been processed
      !   by removing all entries
      call hash_destroy(h_table)
  
      if (associated(src_array)) deallocate(src_array)
  
      src_min_x = INVALID
      src_min_y = INVALID
      src_min_z = INVALID
      src_max_x = INVALID
      src_max_y = INVALID
      src_max_z = INVALID
      src_fieldname = ' '
      src_fname = ' '
 
   end subroutine proc_point_shutdown
 
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: accum_categorical
   !
   ! Purpose: Count the number of source points in each category whose nearest 
   !   neighbor is the specified model grid point.
   !
   ! NOTE: When processing the source tile, those source points that are 
   !   closest to a different model grid point will be added to the totals for 
   !   such grid points; thus, an entire source tile will be processed at a time.
   !   This routine really processes for all model grid points that are 
   !   within a source tile, and not just for a single grid point.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   subroutine accum_categorical(xlat, xlon, istagger, array, &
                                start_i, end_i, start_j, end_j, &
                                start_k, end_k, fieldname, processed_pts, &
                                new_pts, ilevel, msgval, maskval)
   
      implicit none
  
      ! Arguments
      integer, intent(in) :: start_i, end_i, start_j, end_j, start_k, end_k, &
                             istagger, ilevel
      real, intent(in) :: xlat, xlon, msgval, maskval
      real, dimension(start_i:end_i, start_j:end_j, start_k:end_k), intent(inout) :: array
      character (len=128), intent(in) :: fieldname
      type (bitarray), intent(inout) :: processed_pts, new_pts
  
      ! Local variables
      integer :: field_idx, istatus, i, j, ilat, ilon
      integer, pointer, dimension(:,:,:) :: where_maps_to
      real :: rlat, rlon, rx, ry

      rlat = xlat
      if (xlon >= 180.) then
         rlon = xlon - 360.
      else
         rlon = xlon
      end if

      ! Assume source data is on unstaggered grid; specify M for istagger argument
      call get_data_tile(rlat, rlon, ilevel, fieldname, &
                         src_fname, src_array, src_min_x, src_max_x, src_min_y, &
                         src_max_y, src_min_z, src_max_z, src_npts_bdr, &
                         istatus)
  
      src_fieldname = fieldname
      src_level = ilevel

      call hash_insert(h_table, src_fname)
  
      if (istatus /= 0) return
  
      allocate(where_maps_to(src_min_x:src_max_x,src_min_y:src_max_y,2))
      do i=src_min_x,src_max_x
         do j=src_min_y,src_max_y
            where_maps_to(i,j,1) = NOT_PROCESSED 
         end do
      end do
  
      call process_categorical_block(src_array, istagger, where_maps_to, &
                              src_min_x+src_npts_bdr, src_min_y+src_npts_bdr, src_min_z, &
                              src_max_x-src_npts_bdr, src_max_y-src_npts_bdr, src_max_z, &
                              array, start_i, end_i, start_j, end_j, start_k, end_k, &
                              processed_pts, new_pts, ilevel, msgval, maskval)
  
      deallocate(where_maps_to)
 
   end subroutine accum_categorical
 
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: process_categorical_block 
   !
   ! Purpose: To recursively process a subarray of categorical data, assigning
   !   the points in a block to their nearest grid point. The nearest neighbor
   !   may be estimated in some cases; for example, if the four corners of a 
   !   subarray all have the same nearest grid point, all elements in the 
   !   subarray are assigned to that grid point.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   recursive subroutine process_categorical_block(tile_array, istagger, where_maps_to, &
                                   min_i, min_j, min_k, max_i, max_j, max_k, dst_array, &
                                   start_x, end_x, start_y, end_y, start_z, end_z, &
                                   processed_pts, new_pts, ilevel, msgval, maskval, mask_array)
 
      use llxy_module
  
      implicit none
  
      ! Arguments
      integer, intent(in) :: min_i, min_j, min_k, max_i, max_j, max_k, istagger, &
                             start_x, end_x, start_y, end_y, start_z, end_z, ilevel
      integer, dimension(src_min_x:src_max_x,src_min_y:src_max_y,2), intent(inout) :: where_maps_to
      real, intent(in) :: msgval, maskval
      real, dimension(src_min_x:src_max_x,src_min_y:src_max_y,src_min_z:src_max_z), intent(in) :: tile_array
      real, dimension(src_min_x:src_max_x,src_min_y:src_max_y), intent(in), optional :: mask_array
      real, dimension(start_x:end_x,start_y:end_y,start_z:end_z), intent(inout) :: dst_array
      type (bitarray), intent(inout) :: processed_pts, new_pts
  
      ! Local variables
      integer :: x_dest, y_dest, i, j, k, center_i, center_j, current_domain
      real :: lat_corner, lon_corner, rx, ry

      ! Compute the model grid point that the corners of the rectangle to be 
      !   processed map to
      ! Lower-left corner
      if (where_maps_to(min_i,min_j,1) == NOT_PROCESSED) then
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call xytoll(real(min_i), real(min_j), lat_corner, lon_corner, M)
         call select_domain(current_domain)
         call lltoxy(lat_corner, lon_corner, rx, ry, istagger)
         if (real(start_x) <= rx .and. rx <= real(end_x) .and. real(start_y) <= ry .and. ry <= real(end_y)) then
            where_maps_to(min_i,min_j,1) = nint(rx)
            where_maps_to(min_i,min_j,2) = nint(ry)
         else
            where_maps_to(min_i,min_j,1) = OUTSIDE_DOMAIN
         end if
      end if
  
      ! Upper-left corner
      if (where_maps_to(min_i,max_j,1) == NOT_PROCESSED) then
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call xytoll(real(min_i), real(max_j), lat_corner, lon_corner, M)
         call select_domain(current_domain)
         call lltoxy(lat_corner, lon_corner, rx, ry, istagger)
         if (real(start_x) <= rx .and. rx <= real(end_x) .and. real(start_y) <= ry .and. ry <= real(end_y)) then
            where_maps_to(min_i,max_j,1) = nint(rx)
            where_maps_to(min_i,max_j,2) = nint(ry)
         else
            where_maps_to(min_i,max_j,1) = OUTSIDE_DOMAIN
         end if
      end if
  
      ! Upper-right corner
      if (where_maps_to(max_i,max_j,1) == NOT_PROCESSED) then
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call xytoll(real(max_i), real(max_j), lat_corner, lon_corner, M)
         call select_domain(current_domain)
         call lltoxy(lat_corner, lon_corner, rx, ry, istagger)
         if (real(start_x) <= rx .and. rx <= real(end_x) .and. real(start_y) <= ry .and. ry <= real(end_y)) then
            where_maps_to(max_i,max_j,1) = nint(rx)
            where_maps_to(max_i,max_j,2) = nint(ry)
         else
            where_maps_to(max_i,max_j,1) = OUTSIDE_DOMAIN
         end if
      end if
  
      ! Lower-right corner
      if (where_maps_to(max_i,min_j,1) == NOT_PROCESSED) then
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call xytoll(real(max_i), real(min_j), lat_corner, lon_corner, M)
         call select_domain(current_domain) 
         call lltoxy(lat_corner, lon_corner, rx, ry, istagger)
         if (real(start_x) <= rx .and. rx <= real(end_x) .and. real(start_y) <= ry .and. ry <= real(end_y)) then
            where_maps_to(max_i,min_j,1) = nint(rx)
            where_maps_to(max_i,min_j,2) = nint(ry)
         else
            where_maps_to(max_i,min_j,1) = OUTSIDE_DOMAIN
         end if
      end if
  
      ! If all four corners map to same model grid point, accumulate the 
      !   entire rectangle
      if (where_maps_to(min_i,min_j,1) == where_maps_to(min_i,max_j,1) .and. &
          where_maps_to(min_i,min_j,1) == where_maps_to(max_i,max_j,1) .and. &
          where_maps_to(min_i,min_j,1) == where_maps_to(max_i,min_j,1) .and. &
          where_maps_to(min_i,min_j,2) == where_maps_to(min_i,max_j,2) .and. &
          where_maps_to(min_i,min_j,2) == where_maps_to(max_i,max_j,2) .and. &
          where_maps_to(min_i,min_j,2) == where_maps_to(max_i,min_j,2) .and. &
          where_maps_to(min_i,min_j,1) /= OUTSIDE_DOMAIN) then 
         x_dest = where_maps_to(min_i,min_j,1)
         y_dest = where_maps_to(min_i,min_j,2)
         
         ! If this grid point was already given a value from higher-priority source data, 
         !   there is nothing to do.
         if (.not. bitarray_test(processed_pts, x_dest-start_x+1, y_dest-start_y+1)) then
 
            ! If this grid point has never been given a value by this level of source data,
            !   initialize the point
            if (.not. bitarray_test(new_pts, x_dest-start_x+1, y_dest-start_y+1)) then
               do k=start_z,end_z
                  dst_array(x_dest,y_dest,k) = 0.
               end do
            end if
  
            ! Count all the points whose nearest neighbor is this grid point
            if (present(mask_array)) then
               do i=min_i,max_i
                  do j=min_j,max_j
                     ! Ignore masked/missing values in the source data
                     if ((tile_array(i,j,min_k) /= msgval) .and. &
                         (mask_array(i,j) /= maskval)) then
                        if (int(tile_array(i,j,min_k)) >= start_z .and. int(tile_array(i,j,min_k)) <= end_z) then
                           dst_array(x_dest,y_dest,int(tile_array(i,j,min_k))) = &
                               dst_array(x_dest,y_dest,int(tile_array(i,j,min_k))) + 1.0 
                           call bitarray_set(new_pts, x_dest-start_x+1, y_dest-start_y+1)
                        else
                           call mprintf(.true., WARN, 'In source tile %s, point (%i, %i) has '// &
                                        'an invalid category of %i', &
                                        s1=trim(src_fname), i1=i, i2=j, i3=int(tile_array(i,j,min_k)))
                        end if
                     end if
                  end do
               end do
            else
               do i=min_i,max_i
                  do j=min_j,max_j
                     ! Ignore masked/missing values in the source data
                     if (tile_array(i,j,min_k) /= msgval) then
                        if (int(tile_array(i,j,min_k)) >= start_z .and. int(tile_array(i,j,min_k)) <= end_z) then
                           dst_array(x_dest,y_dest,int(tile_array(i,j,min_k))) = &
                               dst_array(x_dest,y_dest,int(tile_array(i,j,min_k))) + 1.0 
                           call bitarray_set(new_pts, x_dest-start_x+1, y_dest-start_y+1)
                        else
                           call mprintf(.true., WARN, 'In source tile %s, point (%i, %i) '// &
                                        'has an invalid category of %i', &
                                        s1=trim(src_fname), i1=i, i2=j, i3=int(tile_array(i,j,min_k)))
                        end if
                     end if
                  end do
               end do
            end if
 
         end if
  
      ! Rectangle is a square of four points, and we can simply deal with each of the points
      else if (((max_i - min_i + 1) <= 2) .and. ((max_j - min_j + 1) <= 2)) then
         do i=min_i,max_i
            do j=min_j,max_j
               x_dest = where_maps_to(i,j,1)
               y_dest = where_maps_to(i,j,2)
     
               if (x_dest /= OUTSIDE_DOMAIN) then 
      
                  if (.not. bitarray_test(processed_pts, x_dest-start_x+1, y_dest-start_y+1)) then
                     if (.not. bitarray_test(new_pts, x_dest-start_x+1, y_dest-start_y+1)) then
                        do k=start_z,end_z
                           dst_array(x_dest,y_dest,k) = 0.
                        end do
                     end if
                     
                     ! Ignore masked/missing values
                     if (present(mask_array)) then
                        if ((tile_array(i,j,min_k) /= msgval) .and. &
                             (mask_array(i,j) /= maskval)) then
                           if (int(tile_array(i,j,min_k)) >= start_z .and. int(tile_array(i,j,min_k)) <= end_z) then
                              dst_array(x_dest,y_dest,int(tile_array(i,j,min_k))) = &
                                  dst_array(x_dest,y_dest,int(tile_array(i,j,min_k))) + 1.0 
                              call bitarray_set(new_pts, x_dest-start_x+1, y_dest-start_y+1)
                           else
                              call mprintf(.true., WARN, 'In source tile %s, point (%i, %i) has '// &
                                           'an invalid category of %i', &
                                           s1=trim(src_fname), i1=i, i2=j, i3=int(tile_array(i,j,min_k)))
                           end if
                        end if
                     else
                        if (tile_array(i,j,min_k) /= msgval) then 
                           if (int(tile_array(i,j,min_k)) >= start_z .and. int(tile_array(i,j,min_k)) <= end_z) then
                              dst_array(x_dest,y_dest,int(tile_array(i,j,min_k))) = &
                                  dst_array(x_dest,y_dest,int(tile_array(i,j,min_k))) + 1.0 
                              call bitarray_set(new_pts, x_dest-start_x+1, y_dest-start_y+1)
                           else
                              call mprintf(.true., WARN, 'In source tile %s, point (%i, %i) has '// &
                                           'an invalid category of %i', &
                                           s1=trim(src_fname), i1=i, i2=j, i3=int(tile_array(i,j,min_k)))
                           end if
                        end if
                     end if
                  end if
     
               end if
            end do
         end do
  
      ! Not all corners map to the same grid point, and the rectangle contains more than
      !   four points
      else
         center_i = (max_i + min_i)/2
         center_j = (max_j + min_j)/2
   
         ! Recursively process lower-left rectangle
         call process_categorical_block(tile_array, istagger, where_maps_to, min_i, min_j, min_k, center_i, &
                    center_j, max_k, dst_array, start_x, end_x, start_y, end_y, start_z, end_z, processed_pts, &
                    new_pts, ilevel, msgval, maskval, mask_array) 
         
         ! Recursively process lower-right rectangle
         if (center_i < max_i) then
            call process_categorical_block(tile_array, istagger, where_maps_to, center_i+1, min_j, min_k, max_i, &
                       center_j, max_k, dst_array, start_x, end_x, start_y, end_y, start_z, end_z, processed_pts, &
                       new_pts, ilevel, msgval, maskval, mask_array) 
         end if
   
         ! Recursively process upper-left rectangle
         if (center_j < max_j) then
            call process_categorical_block(tile_array, istagger, where_maps_to, min_i, center_j+1, min_k, center_i, &
                       max_j, max_k, dst_array, start_x, end_x, start_y, end_y, start_z, end_z, processed_pts, &
                       new_pts, ilevel, msgval, maskval, mask_array) 
         end if
   
         ! Recursively process upper-right rectangle
         if (center_i < max_i .and. center_j < max_j) then
            call process_categorical_block(tile_array, istagger, where_maps_to, center_i+1, center_j+1, min_k, max_i, &
                       max_j, max_k, dst_array, start_x, end_x, start_y, end_y, start_z, end_z, processed_pts, &
                       new_pts, ilevel, msgval, maskval, mask_array) 
         end if
      end if
  
   end subroutine process_categorical_block
 
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: accum_continuous
   !
   ! Purpose: Sum up all of the source data points whose nearest neighbor in the
   !   model grid is the specified model grid point.
   !
   ! NOTE: When processing the source tile, those source points that are 
   !   closest to a different model grid point will be added to the totals for 
   !   such grid points; thus, an entire source tile will be processed at a time.
   !   This routine really processes for all model grid points that are 
   !   within a source tile, and not just for a single grid point.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   subroutine accum_continuous(xlat, xlon, istagger, array, n, &
                                start_i, end_i, start_j, end_j, &
                                start_k, end_k, fieldname, processed_pts, &
                                new_pts, ilevel, msgval, maskval)
   
      implicit none
  
      ! Arguments
      integer, intent(in) :: start_i, end_i, start_j, end_j, start_k, end_k, &
                             istagger, ilevel
      real, intent(in) :: xlat, xlon, msgval, maskval
      real, dimension(start_i:end_i, start_j:end_j, start_k:end_k), intent(inout) :: array, n
      character (len=128), intent(in) :: fieldname
      type (bitarray), intent(inout) :: processed_pts, new_pts
  
      ! Local variables
      integer :: field_idx, istatus, i, j, ilat, ilon
      integer, pointer, dimension(:,:,:) :: where_maps_to
      real :: rlat, rlon, rx, ry

      rlat = xlat
      if (xlon >= 180.) then
         rlon = xlon - 360.
      else
         rlon = xlon
      end if

      ! Assume source data is on unstaggered grid; specify M for istagger argument
      call get_data_tile(rlat, rlon, ilevel, fieldname, &
                         src_fname, src_array, src_min_x, src_max_x, src_min_y, &
                         src_max_y, src_min_z, src_max_z, src_npts_bdr, &
                         istatus)
  
      src_fieldname = fieldname
      src_level = ilevel
  
      call hash_insert(h_table, src_fname)
  
      if (istatus /= 0) then
         src_min_x = INVALID
         src_min_y = INVALID
         src_max_x = INVALID
         src_max_y = INVALID
         return
      end if
  
      allocate(where_maps_to(src_min_x:src_max_x,src_min_y:src_max_y,2))
      do i=src_min_x,src_max_x
         do j=src_min_y,src_max_y
            where_maps_to(i,j,1) = NOT_PROCESSED 
         end do
      end do
  
      call process_continuous_block(src_array, istagger, where_maps_to, &
                               src_min_x+src_npts_bdr, src_min_y+src_npts_bdr, src_min_z, &
                               src_max_x-src_npts_bdr, src_max_y-src_npts_bdr, src_max_z, &
                               array, n, start_i, end_i, start_j, end_j, start_k, end_k, &
                               processed_pts, new_pts, ilevel, msgval, maskval)
  
      deallocate(where_maps_to)
 
   end subroutine accum_continuous
 
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: process_continuous_block 
   !
   ! Purpose: To recursively process a subarray of continuous data, adding the 
   !   points in a block to the sum for their nearest grid point. The nearest 
   !   neighbor may be estimated in some cases; for example, if the four corners 
   !   of a subarray all have the same nearest grid point, all elements in the 
   !   subarray are added to that grid point.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   recursive subroutine process_continuous_block(tile_array, istagger, where_maps_to, &
                                   min_i, min_j, min_k, max_i, max_j, max_k, dst_array, n, &
                                   start_x, end_x, start_y, end_y, start_z, end_z, &
                                   processed_pts, new_pts, ilevel, msgval, maskval, mask_array)
 
      use llxy_module
  
      implicit none
  
      ! Arguments
      integer, intent(in) :: min_i, min_j, min_k, max_i, max_j, max_k, istagger, &
                             start_x, end_x, start_y, end_y, start_z, end_z, ilevel
      integer, dimension(src_min_x:src_max_x,src_min_y:src_max_y,2), intent(inout) :: where_maps_to
      real, intent(in) :: msgval, maskval
      real, dimension(src_min_x:src_max_x,src_min_y:src_max_y,src_min_z:src_max_z), intent(in) :: tile_array
      real, dimension(src_min_x:src_max_x,src_min_y:src_max_y), intent(in), optional :: mask_array
      real, dimension(start_x:end_x,start_y:end_y,start_z:end_z), intent(inout) :: dst_array, n
      type (bitarray), intent(inout) :: processed_pts, new_pts
  
      ! Local variables
      integer :: x_dest, y_dest, i, j, k, center_i, center_j, current_domain
      real :: lat_corner, lon_corner, rx, ry
  
      ! Compute the model grid point that the corners of the rectangle to be 
      !   processed map to
      ! Lower-left corner
      if (where_maps_to(min_i,min_j,1) == NOT_PROCESSED) then
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call xytoll(real(min_i), real(min_j), lat_corner, lon_corner, M)
         call select_domain(current_domain)
         call lltoxy(lat_corner, lon_corner, rx, ry, istagger)
         if (real(start_x) <= rx .and. rx <= real(end_x) .and. real(start_y) <= ry .and. ry <= real(end_y)) then
            where_maps_to(min_i,min_j,1) = nint(rx)
            where_maps_to(min_i,min_j,2) = nint(ry)
         else
            where_maps_to(min_i,min_j,1) = OUTSIDE_DOMAIN
         end if
      end if
  
      ! Upper-left corner
      if (where_maps_to(min_i,max_j,1) == NOT_PROCESSED) then
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call xytoll(real(min_i), real(max_j), lat_corner, lon_corner, M)
         call select_domain(current_domain)
         call lltoxy(lat_corner, lon_corner, rx, ry, istagger)
         if (real(start_x) <= rx .and. rx <= real(end_x) .and. real(start_y) <= ry .and. ry <= real(end_y)) then
            where_maps_to(min_i,max_j,1) = nint(rx)
            where_maps_to(min_i,max_j,2) = nint(ry)
         else
            where_maps_to(min_i,max_j,1) = OUTSIDE_DOMAIN
         end if
      end if
  
      ! Upper-right corner
      if (where_maps_to(max_i,max_j,1) == NOT_PROCESSED) then
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call xytoll(real(max_i), real(max_j), lat_corner, lon_corner, M)
         call select_domain(current_domain)
         call lltoxy(lat_corner, lon_corner, rx, ry, istagger)
         if (real(start_x) <= rx .and. rx <= real(end_x) .and. real(start_y) <= ry .and. ry <= real(end_y)) then
            where_maps_to(max_i,max_j,1) = nint(rx)
            where_maps_to(max_i,max_j,2) = nint(ry)
         else
            where_maps_to(max_i,max_j,1) = OUTSIDE_DOMAIN
         end if
      end if
  
      ! Lower-right corner
      if (where_maps_to(max_i,min_j,1) == NOT_PROCESSED) then
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call xytoll(real(max_i), real(min_j), lat_corner, lon_corner, M)
         call select_domain(current_domain) 
         call lltoxy(lat_corner, lon_corner, rx, ry, istagger)
         if (real(start_x) <= rx .and. rx <= real(end_x) .and. real(start_y) <= ry .and. ry <= real(end_y)) then
            where_maps_to(max_i,min_j,1) = nint(rx)
            where_maps_to(max_i,min_j,2) = nint(ry)
         else
            where_maps_to(max_i,min_j,1) = OUTSIDE_DOMAIN
         end if
      end if
  
      ! If all four corners map to same model grid point, accumulate the 
      !   entire rectangle
      if (where_maps_to(min_i,min_j,1) == where_maps_to(min_i,max_j,1) .and. &
          where_maps_to(min_i,min_j,1) == where_maps_to(max_i,max_j,1) .and. &
          where_maps_to(min_i,min_j,1) == where_maps_to(max_i,min_j,1) .and. &
          where_maps_to(min_i,min_j,2) == where_maps_to(min_i,max_j,2) .and. &
          where_maps_to(min_i,min_j,2) == where_maps_to(max_i,max_j,2) .and. &
          where_maps_to(min_i,min_j,2) == where_maps_to(max_i,min_j,2) .and. &
          where_maps_to(min_i,min_j,1) /= OUTSIDE_DOMAIN) then 
         x_dest = where_maps_to(min_i,min_j,1)
         y_dest = where_maps_to(min_i,min_j,2)
         
         ! If this grid point was already given a value from higher-priority source data, 
         !   there is nothing to do.
         if (.not. bitarray_test(processed_pts, x_dest-start_x+1, y_dest-start_y+1)) then
 
            ! If this grid point has never been given a value by this level of source data,
            !   initialize the point
            if (.not. bitarray_test(new_pts, x_dest-start_x+1, y_dest-start_y+1)) then
               do k=min_k,max_k
                  dst_array(x_dest,y_dest,k) = 0.
               end do
            end if
  
            ! Sum all the points whose nearest neighbor is this grid point
            if (present(mask_array)) then
               do i=min_i,max_i
                  do j=min_j,max_j
                     do k=min_k,max_k
                        ! Ignore masked/missing values in the source data
                        if ((tile_array(i,j,k) /= msgval) .and. &
                            (mask_array(i,j) /= maskval)) then
                           dst_array(x_dest,y_dest,k) = dst_array(x_dest,y_dest,k) + tile_array(i,j,k) 
                           n(x_dest,y_dest,k) = n(x_dest,y_dest,k) + 1.0
                           call bitarray_set(new_pts, x_dest-start_x+1, y_dest-start_y+1)
                        end if
                     end do
                  end do
               end do
            else
               do i=min_i,max_i
                  do j=min_j,max_j
                     do k=min_k,max_k
                        ! Ignore masked/missing values in the source data
                        if (tile_array(i,j,k) /= msgval) then 
                           dst_array(x_dest,y_dest,k) = dst_array(x_dest,y_dest,k) + tile_array(i,j,k) 
                           n(x_dest,y_dest,k) = n(x_dest,y_dest,k) + 1.0
                           call bitarray_set(new_pts, x_dest-start_x+1, y_dest-start_y+1)
                        end if
                     end do
                  end do
               end do
            end if
 
         end if
  
      ! Rectangle is a square of four points, and we can simply deal with each of the points
      else if (((max_i - min_i + 1) <= 2) .and. ((max_j - min_j + 1) <= 2)) then
         do i=min_i,max_i
            do j=min_j,max_j
               x_dest = where_maps_to(i,j,1)
               y_dest = where_maps_to(i,j,2)
     
               if (x_dest /= OUTSIDE_DOMAIN) then 
      
                  if (.not. bitarray_test(processed_pts, x_dest-start_x+1, y_dest-start_y+1)) then
                     if (.not. bitarray_test(new_pts, x_dest-start_x+1, y_dest-start_y+1)) then
                        do k=min_k,max_k
                           dst_array(x_dest,y_dest,k) = 0.
                        end do
                     end if
                     
                     if (present(mask_array)) then
                        do k=min_k,max_k
                           ! Ignore masked/missing values
                           if ((tile_array(i,j,k) /= msgval) .and. &
                                (mask_array(i,j) /= maskval)) then
                              dst_array(x_dest,y_dest,k) = dst_array(x_dest,y_dest,k) + tile_array(i,j,k)
                              n(x_dest,y_dest,k) = n(x_dest,y_dest,k) + 1.0
                              call bitarray_set(new_pts, x_dest-start_x+1, y_dest-start_y+1)
                           end if
                        end do
                     else
                        do k=min_k,max_k
                           ! Ignore masked/missing values
                           if (tile_array(i,j,k) /= msgval) then 
                              dst_array(x_dest,y_dest,k) = dst_array(x_dest,y_dest,k) + tile_array(i,j,k)
                              n(x_dest,y_dest,k) = n(x_dest,y_dest,k) + 1.0
                              call bitarray_set(new_pts, x_dest-start_x+1, y_dest-start_y+1)
                           end if
                        end do
                     end if
                  end if
     
               end if
            end do
         end do
  
      ! Not all corners map to the same grid point, and the rectangle contains more than
      !   four points
      else
         center_i = (max_i + min_i)/2
         center_j = (max_j + min_j)/2
   
         ! Recursively process lower-left rectangle
         call process_continuous_block(tile_array, istagger, where_maps_to, min_i, min_j, min_k, center_i, &
                    center_j, max_k, dst_array, n, start_x, end_x, start_y, end_y, start_z, end_z, processed_pts, &
                    new_pts, ilevel, msgval, maskval, mask_array) 
         
         ! Recursively process lower-right rectangle
         if (center_i < max_i) then
            call process_continuous_block(tile_array, istagger, where_maps_to, center_i+1, min_j, min_k, max_i, &
                       center_j, max_k, dst_array, n, start_x, end_x, start_y, end_y, start_z, end_z, processed_pts, &
                       new_pts, ilevel, msgval, maskval, mask_array) 
         end if
   
         ! Recursively process upper-left rectangle
         if (center_j < max_j) then
            call process_continuous_block(tile_array, istagger, where_maps_to, min_i, center_j+1, min_k, center_i, &
                       max_j, max_k, dst_array, n, start_x, end_x, start_y, end_y, start_z, end_z, processed_pts, &
                       new_pts, ilevel, msgval, maskval, mask_array) 
         end if
   
         ! Recursively process upper-right rectangle
         if (center_i < max_i .and. center_j < max_j) then
            call process_continuous_block(tile_array, istagger, where_maps_to, center_i+1, center_j+1, min_k, max_i, &
                       max_j, max_k, dst_array, n, start_x, end_x, start_y, end_y, start_z, end_z, processed_pts, &
                       new_pts, ilevel, msgval, maskval, mask_array) 
         end if

      end if
  
   end subroutine process_continuous_block
 
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: get_point 
   !
   ! Purpose: For a specified lat/lon and level, return the value of the field
   !   interpolated to or nearest the lat/lon. When ifieldtype = CATEGORICAL,
   !   the source data point nearest the lat/lon is returned, and when 
   !   ifieldtype = CONTINUOUS, the source data is interpolated to the lat/lon.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   function get_point(xlat, xlon, istagger, lvl, fieldname, ifieldtype, &
                      ilevel, interp_type, msgval)
 
      ! Modules
      use interp_module
      use llxy_module
    
      implicit none
  
      ! Arguments
      integer, intent(in) :: istagger, lvl, ifieldtype, ilevel
      real, intent(in) :: xlat, xlon, msgval
      character (len=128), intent(in) :: fieldname
      integer, dimension(:), intent(in) :: interp_type
  
      ! Return value
      real :: get_point
  
      ! Local variables
      integer :: ilat, ilon, field_idx, istatus, current_domain
      real :: rlat, rlon, rx, ry
  
      rlat = xlat
      if (xlon >= 180.) then
         rlon = xlon - 360.
      else
         rlon = xlon
      end if

      ! If tile is in memory, interpolate
      if (ilevel == src_level .and. is_point_in_tile(rlat, rlon, istagger, ilevel) .and. fieldname == src_fieldname) then
  
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call lltoxy(rlat, rlon, rx, ry, M)  ! Assume source data on unstaggered grid
         call select_domain(current_domain)
   
         get_point = interp_sequence(rx, ry, lvl, src_array, src_min_x, src_max_x, src_min_y, &
                                     src_max_y, src_min_z, src_max_z, msgval, interp_type, 1) 
  
      else
  
         call get_data_tile(rlat, rlon, ilevel, fieldname, &
                            src_fname, src_array, src_min_x, src_max_x, src_min_y, &
                            src_max_y, src_min_z, src_max_z, src_npts_bdr, &
                            istatus)
   
         src_fieldname = fieldname
         src_level = ilevel
   
         if (istatus /= 0) then
            get_point = msgval
            return
         end if
   
         current_domain = iget_selected_domain()
         call select_domain(SOURCE_PROJ)
         call lltoxy(rlat, rlon, rx, ry, M)  ! Assume source data on unstaggered grid
         call select_domain(current_domain)
   
         get_point = interp_sequence(rx, ry, lvl, src_array, src_min_x, src_max_x, src_min_y, &
                                     src_max_y, src_min_z, src_max_z, msgval, interp_type, 1) 
      end if
  
      return
 
   end function get_point
 
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: have_processed_tile
   !
   ! Purpose: This funtion returns .true. if the tile of data for 
   !   the specified field has already been processed, and .false. otherwise.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   function have_processed_tile(xlat, xlon, istagger, fieldname, ilevel)
 
      implicit none
  
      ! Arguments
      integer, intent(in) :: istagger, ilevel
      real, intent(in) :: xlat, xlon
      character (len=128), intent(in) :: fieldname
  
      ! Return value
      logical :: have_processed_tile
  
      ! Local variables
      integer :: field_idx, ilat, ilon, istatus
      character (len=256) :: test_fname
  
      call get_tile_fname(test_fname, xlat, xlon, ilevel, fieldname, istatus)
      have_processed_tile = hash_search(h_table, test_fname)
  
      return
 
   end function have_processed_tile
 
 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   ! Name: is_point_in_tile
   !
   ! Purpose: Returns whether the specified lat/lon could be processed
   !   without incurring a file access.
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
   function is_point_in_tile(xlat, xlon, istagger, ilevel)
 
      use llxy_module
    
      implicit none
  
      ! Arguments
      integer, intent(in) :: istagger, ilevel
      real, intent(in) :: xlat, xlon
  
      ! Return value
      logical :: is_point_in_tile
  
      ! Local variables
      integer :: current_domain
      real :: rlat, rlon, rx, ry
  
      rlat = xlat
      if (xlon >= 180.) then
         rlon = xlon - 360.
      else
         rlon = xlon
      end if
  
      current_domain = iget_selected_domain()
      call select_domain(SOURCE_PROJ)
      call lltoxy(rlat, rlon, rx, ry, M)
      call select_domain(current_domain)
  
  !    if (real(src_min_x+src_npts_bdr) <= rx .and. rx <= real(src_max_x-src_npts_bdr) .and. &
  !        real(src_min_y+src_npts_bdr) <= ry .and. ry <= real(src_max_y-src_npts_bdr)) then
! BUG 2006-06-01
!      if (src_min_x+src_npts_bdr <= ceiling(rx) .and. floor(rx) <= src_max_x-src_npts_bdr .and. &
!          src_min_y+src_npts_bdr <= ceiling(ry) .and. floor(ry) <= src_max_y-src_npts_bdr) then
      if (src_min_x+src_npts_bdr <= floor(rx+0.5) .and. ceiling(rx-0.5) <= src_max_x-src_npts_bdr .and. &
          src_min_y+src_npts_bdr <= floor(ry+0.5) .and. ceiling(ry-0.5) <= src_max_y-src_npts_bdr) then
         is_point_in_tile = .true.
      else
         is_point_in_tile = .false.
      end if
   
      return
 
   end function is_point_in_tile

end module proc_point_module
