oceanMask_mod sourceΒΆ

  1module oceanMask_mod
  2  ! MODULE oceanMask_mod (prefix='ocm' category='7. Low-level data objects')
  3  !
  4  !:Purpose:  The horizontal mask indicating land (.false.) and water (.true.) grid points.
  5  !           This mask is either:
  6  !                 * In the case of variables on ocean depth levels, it varies with vertical level.
  7  !                 * In other cases it is a single 2D field used for all variables.
  8  !
  9  use midasMpi_mod
 10  use kdTree2_mod
 11  use horizontalCoord_mod
 12  use verticalCoord_mod
 13  use utilities_mod
 14  implicit none
 15  save
 16  private
 17
 18  ! public structure definition
 19  public :: struct_ocm
 20
 21  ! public subroutines and functions
 22  public :: ocm_readMaskFromFile, ocm_deallocate
 23  public :: ocm_copyMask, ocm_communicateMask
 24  public :: ocm_farFromLand
 25  public :: ocm_copyToInt, ocm_copyFromInt
 26
 27  type struct_ocm
 28    ! This is the derived type of the ocean mask object
 29    integer                   :: nLev
 30    logical, pointer          :: mask(:,:,:) => null()
 31    logical                   :: maskPresent = .false.
 32    type(struct_hco), pointer :: hco
 33  end type struct_ocm
 34
 35  integer, external  :: get_max_rss
 36
 37  contains
 38
 39  !--------------------------------------------------------------------------
 40  ! ocm_readMaskFromFile
 41  !--------------------------------------------------------------------------
 42  subroutine ocm_readMaskFromFile(oceanMask, hco, vco, filename)
 43    !
 44    !:Purpose: Check if any mask fields exist for surface or ocean depth levels.
 45    !
 46    !
 47    implicit none
 48
 49    ! Arguments:
 50    type(struct_ocm),          intent(inout) :: oceanMask
 51    type(struct_hco), pointer, intent(in)    :: hco
 52    type(struct_vco),          intent(in)    :: vco
 53    character(len=*),          intent(in)    :: fileName
 54
 55    ! Locals:
 56    integer :: nulfile, ierr, ip1, ni_file, nj_file, nk_file
 57    integer :: ikey, levIndex
 58    integer :: fnom, fstouv, fclos, fstfrm, fstluk, fstinf, fstsui
 59    integer, allocatable :: mask(:,:)
 60    integer :: maxkeys
 61
 62    ! Check if any mask is present in file, return if not
 63    if ( .not. utl_varNamePresentInFile(' ',fileName_opt=trim(fileName),typvar_opt='@@') ) then
 64      return
 65    end if
 66
 67    !- Open input file
 68    nulfile = 0
 69    write(*,*) 'ocm_readMaskFromFile: file name = ',trim(fileName)
 70    ierr = fnom(nulfile,trim(fileName),'RND+OLD+R/O',0)
 71
 72    if ( ierr >= 0 ) then
 73      maxkeys = fstouv(nulfile,'RND+OLD')
 74    else
 75      call utl_abort('ocm_readMaskFromFile: problem opening input file')
 76    end if
 77
 78    if (nulfile == 0 ) then
 79      call utl_abort('ocm_readMaskFromFile: unit number for input file not valid')
 80    end if
 81
 82    ! Read mask for all fields
 83    if ( vco%nLev_depth > 0 ) then
 84      lev_loop: do levIndex = 1, vco%nLev_depth
 85
 86        ip1 = vco%ip1_depth(levIndex)
 87
 88        ! Make sure that the mask for this variable has the same grid size as hco
 89        ikey = fstinf(nulfile, ni_file, nj_file, nk_file, &
 90                      -1, ' ', ip1, -1, -1, '@@', ' ')
 91
 92        if (ikey < 0) then
 93          write(*,*) 'ocm_readMaskFromFile: searched for mask with ip1 = ', ip1
 94          call utl_abort('ocm_readMaskFromFile: cannot find mask for this ip1 in file ' // trim(fileName))
 95        end if
 96
 97        do while (ni_file /= hco%ni .or. nj_file /= hco%nj)
 98          ikey = fstsui(nulfile, ni_file, nj_file, nk_file)
 99        end do
100
101        if (ni_file == hco%ni .and. nj_file == hco%nj) then
102          write(*,*) 'ocm_readMaskFromFile: read mask for ip1 = ', ip1
103          if (.not. associated(oceanMask%mask)) then
104            call ocm_allocate(oceanMask,hco,vco%nLev_depth)
105          end if
106          if (.not. allocated(mask)) allocate(mask(hco%ni,hco%nj))
107          ierr = fstluk(mask(:,:), ikey, ni_file, nj_file, nk_file)
108          call ocm_copyFromInt(oceanMask,mask,levIndex)
109          if (ierr < 0) then
110            call utl_abort('ocm_readMaskFromFile: error when reading mask record')
111          end if
112        else
113          ! Special cases for variables that are on a different horizontal grid in LAM (e.g. TG)
114          write(*,*)
115          write(*,*) 'ocm_readMaskFromFile: mask is on a different horizontal grid'
116          write(*,*) 'ni = ', ni_file, hco%ni, ', nj = ', nj_file, hco%nj
117          call utl_abort('ocm_readMaskFromFile: This is not allowed at the moment')
118        end if
119      end do lev_loop
120
121    else
122      ! ***No depth levels, so just look for any mask field***
123
124      ! Make sure that the mask for this variable has the same grid size as hco
125      ikey = fstinf(nulfile, ni_file, nj_file, nk_file, &
126                    -1, ' ', -1, -1, -1, '@@', ' ')
127
128      if (ikey < 0) then
129        call utl_abort('ocm_readMaskFromFile: cannot find any mask in file ' // trim(fileName))
130      end if
131
132      do while (ni_file /= hco%ni .or. nj_file /= hco%nj)
133        ikey = fstsui(nulfile, ni_file, nj_file, nk_file)
134      end do
135
136      if (ni_file == hco%ni .and. nj_file == hco%nj) then
137        write(*,*) 'ocm_readMaskFromFile: reading mask'
138        if (.not. associated(oceanMask%mask)) then
139          call ocm_allocate(oceanMask,hco,1)
140          if (.not. allocated(mask)) allocate(mask(hco%ni,hco%nj))
141          ierr = fstluk(mask(:,:), ikey, ni_file, nj_file, nk_file)
142          call ocm_copyFromInt(oceanMask,mask,1)
143          if (ierr < 0) then
144            call utl_abort('ocm_readMaskFromFile: error when reading mask record')
145          end if
146        end if
147      else
148        ! Special cases for variables that are on a different horizontal grid in LAM (e.g. TG)
149        write(*,*)
150        write(*,*) 'ocm_readMaskFromFile: mask is on a different horizontal grid'
151        write(*,*) 'ni = ', ni_file, hco%ni, ', nj = ', nj_file, hco%nj
152        call utl_abort('ocm_readMaskFromFile: This is not allowed at the moment')
153      end if
154
155    end if
156
157    ierr = fstfrm(nulfile)
158    ierr = fclos(nulfile)        
159
160    if (allocated(mask)) deallocate(mask)
161
162  end subroutine ocm_readMaskFromFile
163
164  !--------------------------------------------------------------------------
165  ! ocm_farFromLand
166  !--------------------------------------------------------------------------
167  function ocm_farFromLand(oceanMask, levIndex, lon, lat, distanceToLand) result(farFromLand)
168    !
169    !:Purpose: Determine if the supplied lat/lat location is far from the
170    !           nearest land based on the supplied 'distanceToLand'.
171    !
172    implicit none
173
174    ! Arguments:
175    type(struct_ocm), intent(in) :: oceanMask
176    integer,          intent(in) :: levIndex
177    real(8),          intent(in) :: lon
178    real(8),          intent(in) :: lat
179    real(8),          intent(in) :: distanceToLand
180    ! Result:
181    logical                      :: farFromLand
182
183    ! Locals:
184    integer, parameter           :: maxNumLocalGridPointsSearch = 200000
185    type(kdtree2), save, pointer :: tree => null()
186    integer                      :: ni, nj, xIndex, yIndex, gridIndex
187    integer                      :: numTotalLandPoints, numLocalGridPointsFound
188    real(kdkind), allocatable    :: positionArray(:,:)
189    type(kdtree2_result)         :: searchResults(maxNumLocalGridPointsSearch)
190    real(kdkind)                 :: searchRadiusSquared
191    real(kdkind)                 :: refPosition(3)
192
193    ! do some basic checks
194    if (.not.oceanMask%maskPresent .or. .not.associated(oceanMask%mask)) then
195      call utl_abort('ocm_farFromLand: mask is not allocated')
196    end if
197    if (levIndex < 0 .or. levIndex > oceanMask%nLev) then
198      call utl_abort('ocm_farFromLand: specified levIndex not valid')
199    end if
200
201    ni = oceanMask%hco%ni
202    nj = oceanMask%hco%nj
203    searchRadiusSquared = (1.1D0 * distanceToLand)**2
204
205    ! create the kdtree on the first call
206    if (.not. associated(tree)) then
207      write(*,*) 'ocm_farFromLand: start creating kdtree'
208      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
209
210      numTotalLandPoints = count(.not. oceanMask%mask(:,:,levIndex))
211      allocate(positionArray(3,numTotalLandPoints))
212
213      gridIndex = 0
214      do xIndex = 1, ni
215        do yIndex = 1, nj
216          if (.not. oceanMask%mask(xIndex,yIndex,levIndex)) then
217            gridIndex = gridIndex + 1
218            positionArray(:,gridIndex) = &
219                 kdtree2_3dPosition(real(oceanMask%hco%lon2d_4(xIndex,yIndex),8), &
220                                    real(oceanMask%hco%lat2d_4(xIndex,yIndex),8))
221          end if
222        end do
223      end do
224      tree => kdtree2_create(positionArray, sort=.true., rearrange=.true.) 
225      write(*,*) 'ocm_farFromLand: done creating kdtree'
226      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
227
228    end if
229
230    ! do the search
231    refPosition(:) = kdtree2_3dPosition(lon, lat)
232
233    call kdtree2_r_nearest(tp=tree, qv=refPosition, r2=searchRadiusSquared, &
234                           nfound=numLocalGridPointsFound, &
235                           nalloc=maxNumLocalGridPointsSearch, results=searchResults)
236    if (numLocalGridPointsFound > maxNumLocalGridPointsSearch) then
237      call utl_abort('ocm_farFromLand: the parameter maxNumLocalGridPointsSearch must be increased')
238    end if
239    if (numLocalGridPointsFound == 0) then
240      farFromLand = .true.
241    else
242      farFromLand = ( sqrt(searchResults(1)%dis) > distanceToLand )
243    end if
244
245  end function ocm_farFromLand
246
247  !--------------------------------------------------------------------------
248  ! ocm_copyMask
249  !--------------------------------------------------------------------------
250  subroutine ocm_copyMask(oceanMask_in, oceanMask_out, beSilent_opt)
251    !
252    !:Purpose: Copy the mask data from one instance of oceanMask to
253    !           another. If the destination instance is not already
254    !           allocated, then this will also be done.
255    !
256    implicit none
257
258    ! Arguments:
259    type(struct_ocm),  intent(in)    :: oceanMask_in
260    logical, optional, intent(in)    :: beSilent_opt
261    type(struct_ocm),  intent(inout) :: oceanMask_out
262
263    ! Locals:
264    logical :: beSilent
265
266    if ( present(beSilent_opt) ) then
267      beSilent = beSilent_opt
268    else
269      beSilent = .false.
270    end if
271    
272    if (.not.oceanMask_in%maskPresent .or. .not.associated(oceanMask_in%mask)) then
273      if ( .not. beSilent ) write(*,*) 'ocm_copyMask: no input mask, do nothing'
274      return
275    end if
276
277    if (.not.associated(oceanMask_out%mask)) then
278      call ocm_allocate(oceanMask_out, oceanMask_in%hco, oceanMask_in%nLev)
279    end if
280
281    if ( .not. beSilent ) write(*,*) 'ocm_copyMask: copying over the horizontal mask'
282    oceanMask_out%mask(:,:,:) = oceanMask_in%mask(:,:,:)
283    oceanMask_out%maskPresent = .true.
284
285  end subroutine ocm_copyMask
286
287  !--------------------------------------------------------------------------
288  ! ocm_communicateMask
289  !--------------------------------------------------------------------------
290  subroutine ocm_communicateMask(oceanMask)
291    !
292    !:Purpose: Copy mask fields from task 0 to all others
293    !
294    implicit none
295
296    ! Arguments:
297    type(struct_ocm), intent(inout) :: oceanMask
298
299    ! Locals:
300    integer                   :: ierr
301    type(struct_hco), pointer :: hco_temp
302
303    write(*,*) 'ocm_communicateMask: starting'
304
305    call rpn_comm_bcast(oceanMask%maskPresent, 1,  &
306                        'MPI_LOGICAL', 0, 'GRID', ierr)
307    if (.not.oceanMask%maskPresent) then
308      write(*,*) 'ocm_communicateMask: mask not present, return'
309      return
310    end if
311    
312    call rpn_comm_bcast(oceanMask%nLev, 1,  &
313                        'MPI_INTEGER', 0, 'GRID', ierr)
314
315    ! special treatment of hco object since EZscintID not properly communicated
316    nullify(hco_temp)
317    if (mmpi_myid > 0 .and. associated(oceanMask%hco)) then
318      hco_temp => oceanMask%hco
319      nullify(oceanMask%hco)
320    end if
321    call hco_mpiBcast(oceanMask%hco)
322
323    if (associated(hco_temp)) then
324      call hco_deallocate(oceanMask%hco)
325      oceanMask%hco => hco_temp
326    end if
327    
328    if (.not.associated(oceanMask%mask)) then
329      call ocm_allocate(oceanMask,oceanMask%hco,oceanMask%nLev)
330    end if
331    call rpn_comm_bcast(oceanMask%mask, oceanMask%hco%ni*oceanMask%hco%nj*1,  &
332                        'MPI_LOGICAL', 0, 'GRID', ierr)
333
334    write(*,*) 'ocm_communicateMask: finished'
335
336  end subroutine ocm_communicateMask
337
338  !--------------------------------------------------------------------------
339  ! ocm_allocate
340  !--------------------------------------------------------------------------
341  subroutine ocm_allocate(oceanMask,hco,nLev)
342    !
343    !:Purpose: Allocate the object, if it isn't already.
344    !
345    implicit none
346
347    ! Arguments:
348    type(struct_ocm),          intent(inout) :: oceanMask
349    type(struct_hco), pointer, intent(in)    :: hco
350    integer,                   intent(in)    :: nLev
351
352    if (.not.associated(oceanMask%mask)) then
353      allocate(oceanMask%mask(hco%ni,hco%nj,nLev))
354      oceanMask%maskPresent = .true.
355      oceanMask%hco         => hco
356      oceanMask%nLev        = nLev
357    end if
358    
359  end subroutine ocm_allocate
360
361  !--------------------------------------------------------------------------
362  ! ocm_deallocate
363  !--------------------------------------------------------------------------
364  subroutine ocm_deallocate(oceanMask)
365    !
366    !:Purpose: Deallocate object.
367    !
368    implicit none
369
370    ! Arguments:
371    type(struct_ocm), intent(inout) :: oceanMask
372
373    if (associated(oceanMask%mask)) then
374      deallocate(oceanMask%mask)
375      nullify(oceanMask%mask)
376      nullify(oceanMask%hco)
377      oceanMask%maskPresent = .false.
378      oceanMask%nLev        = 0
379    end if
380    
381  end subroutine ocm_deallocate
382
383  !--------------------------------------------------------------------------
384  ! ocm_logicalToInt
385  !--------------------------------------------------------------------------
386  subroutine ocm_copyToInt(oceanMask, intArray, maskLev)
387    !
388    !:Purpose: Convert the selected level of the logical oceanMask
389    !           object into integer values where true is 1 and false is 0.
390    !
391    implicit none
392
393    ! Arguments:
394    type(struct_ocm), intent(inout) :: oceanMask
395    integer,          intent(out)   :: intArray(:,:)
396    integer,          intent(in)    :: maskLev
397
398    intArray(:,:) = 0
399    where(oceanMask%mask(:,:,maskLev)) intArray(:,:) = 1
400    
401  end subroutine ocm_copyToInt
402
403  !--------------------------------------------------------------------------
404  ! ocm_logicalToInt
405  !--------------------------------------------------------------------------
406  subroutine ocm_copyFromInt(oceanMask, intArray, maskLev)
407    !
408    !:Purpose: Convert a 2D integer array into logical values
409    !           where true is 1 and false is 0 and copy into
410    !           the selected level of the oceanMask object.
411    !
412    implicit none
413
414    ! Arguments:
415    type(struct_ocm), intent(inout) :: oceanMask
416    integer,          intent(in)    :: intArray(:,:)
417    integer,          intent(in)    :: maskLev
418
419    oceanMask%mask(:,:,maskLev) = .false.
420    where(intArray(:,:) == 1) oceanMask%mask(:,:,maskLev) = .true.
421    
422  end subroutine ocm_copyFromInt
423
424end module oceanMask_mod