# 1 "../horiz_interp/horiz_interp_bilinear.F90" !*********************************************************************** !* GNU Lesser General Public License !* !* This file is part of the GFDL Flexible Modeling System (FMS). !* !* FMS is free software: you can redistribute it and/or modify it under !* the terms of the GNU Lesser General Public License as published by !* the Free Software Foundation, either version 3 of the License, or (at !* your option) any later version. !* !* FMS is distributed in the hope that it will be useful, but WITHOUT !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License !* for more details. !* !* You should have received a copy of the GNU Lesser General Public !* License along with FMS. If not, see . !*********************************************************************** module horiz_interp_bilinear_mod ! Zhi Liang ! ! ! Performs spatial interpolation between grids using bilinear interpolation ! ! ! This module can interpolate data from regular rectangular grid ! to rectangular/tripolar grid. The interpolation scheme is bilinear interpolation. ! There is an optional mask field for missing input data. ! An optional output mask field may be used in conjunction with ! the input mask to show where output data exists. ! use mpp_mod, only: mpp_error, FATAL, stdout, mpp_pe, mpp_root_pe use fms_mod, only: write_version_number use constants_mod, only: PI use horiz_interp_type_mod, only: horiz_interp_type, stats implicit none private public :: horiz_interp_bilinear_new, horiz_interp_bilinear, horiz_interp_bilinear_del public :: horiz_interp_bilinear_init !--- public interface interface horiz_interp_bilinear_new module procedure horiz_interp_bilinear_new_1d module procedure horiz_interp_bilinear_new_2d end interface real, parameter :: epsln=1.e-10 integer, parameter :: DUMMY = -999 !----------------------------------------------------------------------- ! Include variable "version" to be written to log file. # 1 "../include/file_version.h" 1 ! -*-f90-*- !*********************************************************************** !* GNU Lesser General Public License !* !* This file is part of the GFDL Flexible Modeling System (FMS). !* !* FMS is free software: you can redistribute it and/or modify it under !* the terms of the GNU Lesser General Public License as published by !* the Free Software Foundation, either version 3 of the License, or (at !* your option) any later version. !* !* FMS is distributed in the hope that it will be useful, but WITHOUT !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License !* for more details. !* !* You should have received a copy of the GNU Lesser General Public !* License along with FMS. If not, see . !*********************************************************************** # 23 character(len=*), parameter :: version = 'unknown' # 62 "../horiz_interp/horiz_interp_bilinear.F90" 2 logical :: module_is_initialized = .FALSE. contains !####################################################################### ! ! ! writes version number to logfile.out ! ! ! writes version number to logfile.out ! subroutine horiz_interp_bilinear_init if(module_is_initialized) return call write_version_number("HORIZ_INTERP_BILINEAR_MOD", version) module_is_initialized = .true. end subroutine horiz_interp_bilinear_init ! !######################################################################## subroutine horiz_interp_bilinear_new_1d ( Interp, lon_in, lat_in, lon_out, lat_out, & verbose, src_modulo ) !----------------------------------------------------------------------- type(horiz_interp_type), intent(inout) :: Interp real, intent(in), dimension(:) :: lon_in , lat_in real, intent(in), dimension(:,:) :: lon_out, lat_out integer, intent(in), optional :: verbose logical, intent(in), optional :: src_modulo logical :: src_is_modulo integer :: nlon_in, nlat_in, nlon_out, nlat_out, n, m integer :: ie, is, je, js, ln_err, lt_err, warns, unit real :: wtw, wte, wts, wtn, lon, lat, tpi, hpi real :: glt_min, glt_max, gln_min, gln_max, min_lon, max_lon warns = 0 if(present(verbose)) warns = verbose src_is_modulo = .true. if (present(src_modulo)) src_is_modulo = src_modulo hpi = 0.5*pi tpi = 4.0*hpi glt_min = hpi glt_max = -hpi gln_min = tpi gln_max = -tpi min_lon = 0.0 max_lon = tpi ln_err = 0 lt_err = 0 !----------------------------------------------------------------------- allocate ( Interp % wti (size(lon_out,1),size(lon_out,2),2), & Interp % wtj (size(lon_out,1),size(lon_out,2),2), & Interp % i_lon (size(lon_out,1),size(lon_out,2),2), & Interp % j_lat (size(lon_out,1),size(lon_out,2),2)) !----------------------------------------------------------------------- nlon_in = size(lon_in(:)) ; nlat_in = size(lat_in(:)) nlon_out = size(lon_out, 1); nlat_out = size(lon_out, 2) Interp%nlon_src = nlon_in; Interp%nlat_src = nlat_in Interp%nlon_dst = nlon_out; Interp%nlat_dst = nlat_out if(src_is_modulo) then if(lon_in(nlon_in) - lon_in(1) .gt. tpi + epsln) & call mpp_error(FATAL,'horiz_interp_bilinear_mod: '// & 'The range of source grid longitude should be no larger than tpi') if(lon_in(1) .lt. 0.0 .OR. lon_in(nlon_in) > tpi ) then min_lon = lon_in(1) max_lon = lon_in(nlon_in) endif endif do n = 1, nlat_out do m = 1, nlon_out lon = lon_out(m,n) lat = lat_out(m,n) if(src_is_modulo) then if(lon .lt. min_lon) then lon = lon + tpi else if(lon .gt. max_lon) then lon = lon - tpi endif else ! when the input grid is in not cyclic, the output grid should located inside ! the input grid if((lon .lt. lon_in(1)) .or. (lon .gt. lon_in(nlon_in))) & call mpp_error(FATAL,'horiz_interp_bilinear_mod: ' //& 'when input grid is not modulo, output grid should locate inside input grid') endif glt_min = min(lat,glt_min); glt_max = max(lat,glt_max) gln_min = min(lon,gln_min); gln_max = max(lon,gln_max) is = indp(lon, lon_in ) if( lon_in(is) .gt. lon ) is = max(is-1,1) if( lon_in(is) .eq. lon .and. is .eq. nlon_in) is = max(is - 1,1) ie = min(is+1,nlon_in) if(lon_in(is) .ne. lon_in(ie) .and. lon_in(is) .le. lon) then wtw = ( lon_in(ie) - lon) / (lon_in(ie) - lon_in(is) ) else ! east or west of the last data value. this could be because a ! cyclic condition is needed or the dataset is too small. ln_err = 1 ie = 1 is = nlon_in if (lon_in(ie) .ge. lon ) then wtw = (lon_in(ie) -lon)/(lon_in(ie)-lon_in(is)+tpi+epsln) else wtw = (lon_in(ie) -lon+tpi+epsln)/(lon_in(ie)-lon_in(is)+tpi+epsln) endif endif wte = 1. - wtw js = indp(lat, lat_in ) if( lat_in(js) .gt. lat ) js = max(js - 1, 1) if( lat_in(js) .eq. lat .and. js .eq. nlat_in) js = max(js - 1, 1) je = min(js + 1, nlat_in) if ( lat_in(js) .ne. lat_in(je) .and. lat_in(js) .le. lat) then wts = ( lat_in(je) - lat )/(lat_in(je)-lat_in(js)) else ! north or south of the last data value. this could be because a ! pole is not included in the data set or the dataset is too small. ! in either case extrapolate north or south lt_err = 1 wts = 1. endif wtn = 1. - wts Interp % i_lon (m,n,1) = is; Interp % i_lon (m,n,2) = ie Interp % j_lat (m,n,1) = js; Interp % j_lat (m,n,2) = je Interp % wti (m,n,1) = wtw Interp % wti (m,n,2) = wte Interp % wtj (m,n,1) = wts Interp % wtj (m,n,2) = wtn enddo enddo unit = stdout() if (ln_err .eq. 1 .and. warns > 0) then write (unit,'(/,(1x,a))') & '==> Warning: the geographic data set does not extend far ', & ' enough east or west - a cyclic boundary ', & ' condition was applied. check if appropriate ' write (unit,'(/,(1x,a,2f8.4))') & ' data required between longitudes:', gln_min, gln_max, & ' data set is between longitudes:', lon_in(1), lon_in(nlon_in) warns = warns - 1 endif if (lt_err .eq. 1 .and. warns > 0) then write (unit,'(/,(1x,a))') & '==> Warning: the geographic data set does not extend far ',& ' enough north or south - extrapolation from ',& ' the nearest data was applied. this may create ',& ' artificial gradients near a geographic pole ' write (unit,'(/,(1x,a,2f8.4))') & ' data required between latitudes:', glt_min, glt_max, & ' data set is between latitudes:', lat_in(1), lat_in(nlat_in) endif return end subroutine horiz_interp_bilinear_new_1d !####################################################################### ! ! ! Initialization routine. ! ! ! Allocates space and initializes a derived-type variable ! that contains pre-computed interpolation indices and weights. ! ! ! ! ! Longitude (in radians) for source data grid. ! ! ! Latitude (in radians) for source data grid. ! ! ! Longitude (in radians) for source data grid. ! ! ! Latitude (in radians) for source data grid. ! ! ! logical variable to indicate if the boundary condition along zonal boundary ! is cyclic or not. When true, the zonal boundary condition is cyclic. ! ! ! flag for the amount of print output. ! ! ! A derived-type variable containing indices and weights used for subsequent ! interpolations. To reinitialize this variable for a different grid-to-grid ! interpolation you must first use the "horiz_interp_del" interface. ! subroutine horiz_interp_bilinear_new_2d ( Interp, lon_in, lat_in, lon_out, lat_out, & verbose, src_modulo, new_search, no_crash_when_not_found ) !----------------------------------------------------------------------- type(horiz_interp_type), intent(inout) :: Interp real, intent(in), dimension(:,:) :: lon_in , lat_in real, intent(in), dimension(:,:) :: lon_out, lat_out integer, intent(in), optional :: verbose logical, intent(in), optional :: src_modulo logical, intent(in), optional :: new_search logical, intent(in), optional :: no_crash_when_not_found integer :: warns logical :: src_is_modulo integer :: nlon_in, nlat_in, nlon_out, nlat_out integer :: m, n, is, ie, js, je, num_solution real :: lon, lat, quadra, x, y, y1, y2 real :: a1, b1, c1, d1, a2, b2, c2, d2, a, b, c real :: lon1, lat1, lon2, lat2, lon3, lat3, lon4, lat4 real :: tpi, lon_min, lon_max real :: epsln2 logical :: use_new_search, no_crash tpi = 2.0*pi warns = 0 if(present(verbose)) warns = verbose src_is_modulo = .true. if (present(src_modulo)) src_is_modulo = src_modulo use_new_search = .false. if (present(new_search)) use_new_search = new_search no_crash = .false. if(present(no_crash_when_not_found)) no_crash = no_crash_when_not_found ! make sure lon and lat has the same dimension if(size(lon_out,1) /= size(lat_out,1) .or. size(lon_out,2) /= size(lat_out,2) ) & call mpp_error(FATAL,'horiz_interp_bilinear_mod: when using bilinear ' // & 'interplation, the output grids should be geographical grids') if(size(lon_in,1) /= size(lat_in,1) .or. size(lon_in,2) /= size(lat_in,2) ) & call mpp_error(FATAL,'horiz_interp_bilinear_mod: when using bilinear '// & 'interplation, the input grids should be geographical grids') !--- get the grid size nlon_in = size(lon_in,1) ; nlat_in = size(lat_in,2) nlon_out = size(lon_out,1); nlat_out = size(lon_out,2) Interp%nlon_src = nlon_in; Interp%nlat_src = nlat_in Interp%nlon_dst = nlon_out; Interp%nlat_dst = nlat_out allocate ( Interp % wti (size(lon_out,1),size(lon_out,2),2), & Interp % wtj (size(lon_out,1),size(lon_out,2),2), & Interp % i_lon (size(lon_out,1),size(lon_out,2),2), & Interp % j_lat (size(lon_out,1),size(lon_out,2),2)) !--- first fine the neighbor points for the destination points. if(use_new_search) then epsln2 = epsln*1e5 call find_neighbor_new(Interp, lon_in, lat_in, lon_out, lat_out, src_is_modulo, no_crash) else epsln2 = epsln call find_neighbor(Interp, lon_in, lat_in, lon_out, lat_out, src_is_modulo) endif !*************************************************************************** ! Algorithm explanation (from disscussion with Steve Garner ) * ! * ! lon(x,y) = a1*x + b1*y + c1*x*y + d1 (1) * ! lat(x,y) = a2*x + b2*y + c2*x*y + d2 (2) * ! f (x,y) = a3*x + b3*y + c3*x*y + d3 (3) * ! with x and y is between 0 and 1. * ! lon1 = lon(0,0) = d1, lat1 = lat(0,0) = d2 * ! lon2 = lon(1,0) = a1+d1, lat2 = lat(1,0) = a2+d2 * ! lon3 = lon(1,1) = a1+b1+c1+d1, lat3 = lat(1,1) = a2+b2+c2+d2 * ! lon4 = lon(0,1) = b1+d1, lat4 = lat(0,1) = b2+d2 * ! where (lon1,lat1),(lon2,lat2),(lon3,lat3),(lon4,lat4) represents * ! the four corners starting from the left lower corner of grid box * ! that encloses a destination grid ( the rotation direction is * ! counterclockwise ). With these conditions, we get * ! a1 = lon2-lon1, a2 = lat2-lat1 * ! b1 = lon4-lon1, b2 = lat4-lat1 * ! c1 = lon3-lon2-lon4+lon1, c2 = lat3-lat2-lat4+lat1 * ! d1 = lon1 d2 = lat1 * ! So given any point (lon,lat), from equation (1) and (2) we can * ! solve (x,y). * ! From equation (3) * ! f1 = f(0,0) = d3, f2 = f(1,0) = a3+d3 * ! f3 = f(1,1) = a3+b3+c3+d3, f4 = f(0,1) = b3+d3 * ! we obtain * ! a3 = f2-f1, b3 = f4-f1 * ! c3 = f3-f2-f4+f1, d3 = f1 * ! at point (lon,lat) ---> (x,y) * ! f(x,y) = (f2-f1)x + (f4-f1)y + (f3-f2-f4+f1)xy + f1 * ! = f1*(1-x)*(1-y) + f2*x*(1-y) + f3*x*y + f4*y*(1-x) * ! wtw=1-x; wte=x; wts=1-y; xtn=y * ! * !*************************************************************************** lon_min = minval(lon_in); lon_max = maxval(lon_in); !--- calculate the weight do n = 1, nlat_out do m = 1, nlon_out lon = lon_out(m,n) lat = lat_out(m,n) if(lon .lt. lon_min) then lon = lon + tpi else if(lon .gt. lon_max) then lon = lon - tpi endif is = Interp%i_lon(m,n,1); ie = Interp%i_lon(m,n,2) js = Interp%j_lat(m,n,1); je = Interp%j_lat(m,n,2) if( is == DUMMY) cycle lon1 = lon_in(is,js); lat1 = lat_in(is,js); lon2 = lon_in(ie,js); lat2 = lat_in(ie,js); lon3 = lon_in(ie,je); lat3 = lat_in(ie,je); lon4 = lon_in(is,je); lat4 = lat_in(is,je); if(lon .lt. lon_min) then lon1 = lon1 -tpi; lon4 = lon4 - tpi else if(lon .gt. lon_max) then lon2 = lon2 +tpi; lon3 = lon3 + tpi endif a1 = lon2-lon1 b1 = lon4-lon1 c1 = lon1+lon3-lon4-lon2 d1 = lon1 a2 = lat2-lat1 b2 = lat4-lat1 c2 = lat1+lat3-lat4-lat2 d2 = lat1 !--- the coefficient of the quadratic equation a = b2*c1-b1*c2 b = a1*b2-a2*b1+c1*d2-c2*d1+c2*lon-c1*lat c = a2*lon-a1*lat+a1*d2-a2*d1 quadra = b*b-4.*a*c if(abs(quadra) < epsln) quadra = 0.0 if(quadra < 0.0) call mpp_error(FATAL, & "horiz_interp_bilinear_mod: No solution existed for this quadratic equation") if ( abs(a) .lt. epsln2) then ! a = 0 is a linear equation if( abs(b) .lt. epsln) call mpp_error(FATAL, & "horiz_interp_bilinear_mod: no unique solution existed for this linear equation") y = -c/b else y1 = 0.5*(-b+sqrt(quadra))/a y2 = 0.5*(-b-sqrt(quadra))/a if(abs(y1) < epsln2) y1 = 0.0 if(abs(y2) < epsln2) y2 = 0.0 if(abs(1.0-y1) < epsln2) y1 = 1.0 if(abs(1.0-y2) < epsln2) y2 = 1.0 num_solution = 0 if(y1 >= 0.0 .and. y1 <= 1.0) then y = y1 num_solution = num_solution +1 endif if(y2 >= 0.0 .and. y2 <= 1.0) then y = y2 num_solution = num_solution + 1 endif if(num_solution == 0) then call mpp_error(FATAL, "horiz_interp_bilinear_mod: No solution found") else if(num_solution == 2) then call mpp_error(FATAL, "horiz_interp_bilinear_mod: Two solutions found") endif endif if(abs(a1+c1*y) < epsln) call mpp_error(FATAL, & "horiz_interp_bilinear_mod: the denomenator is 0") if(abs(y) < epsln2) y = 0.0 if(abs(1.0-y) < epsln2) y = 1.0 x = (lon-b1*y-d1)/(a1+c1*y) if(abs(x) < epsln2) x = 0.0 if(abs(1.0-x) < epsln2) x = 1.0 ! x and y should be between 0 and 1. !! Added for ECDA if(use_new_search) then if (x < 0.0) x = 0.0 ! snz if (y < 0.0) y = 0.0 ! snz if (x > 1.0) x = 1.0 if (y > 1.0) y = 1.0 endif if( x>1. .or. x<0. .or. y>1. .or. y < 0.) call mpp_error(FATAL, & "horiz_interp_bilinear_mod: weight should be between 0 and 1") Interp % wti(m,n,1)=1.0-x; Interp % wti(m,n,2)=x Interp % wtj(m,n,1)=1.0-y; Interp % wtj(m,n,2)=y enddo enddo end subroutine horiz_interp_bilinear_new_2d ! !####################################################################### ! this routine will search the source grid to fine the grid box that encloses ! each destination grid. subroutine find_neighbor( Interp, lon_in, lat_in, lon_out, lat_out, src_modulo ) type(horiz_interp_type), intent(inout) :: Interp real, intent(in), dimension(:,:) :: lon_in , lat_in real, intent(in), dimension(:,:) :: lon_out, lat_out logical, intent(in) :: src_modulo integer :: nlon_in, nlat_in, nlon_out, nlat_out integer :: max_step, n, m, l, i, j, ip1, jp1, step integer :: is, js, jstart, jend, istart, iend, npts integer, allocatable, dimension(:) :: ilon, jlat real :: lon_min, lon_max, lon, lat, tpi logical :: found real :: lon1, lat1, lon2, lat2, lon3, lat3, lon4, lat4 tpi = 2.0*pi nlon_in = size(lon_in,1) ; nlat_in = size(lat_in,2) nlon_out = size(lon_out,1); nlat_out = size(lon_out,2) lon_min = minval(lon_in); lon_max = maxval(lon_in); max_step = max(nlon_in,nlat_in) ! can be adjusted if needed allocate(ilon(5*max_step), jlat(5*max_step) ) do n = 1, nlat_out do m = 1, nlon_out found = .false. lon = lon_out(m,n) lat = lat_out(m,n) if(src_modulo) then if(lon .lt. lon_min) then lon = lon + tpi else if(lon .gt. lon_max) then lon = lon - tpi endif else if(lon .lt. lon_min .or. lon .gt. lon_max ) & call mpp_error(FATAL,'horiz_interp_bilinear_mod: ' //& 'when input grid is not modulo, output grid should locate inside input grid') endif !--- search for the surrounding four points locatioon. if(m==1 .and. n==1) then J_LOOP: do j = 1, nlat_in-1 do i = 1, nlon_in ip1 = i+1 jp1 = j+1 if(i==nlon_in) then if(src_modulo)then ip1 = 1 else cycle endif endif lon1 = lon_in(i, j); lat1 = lat_in(i,j) lon2 = lon_in(ip1,j); lat2 = lat_in(ip1,j) lon3 = lon_in(ip1,jp1); lat3 = lat_in(ip1,jp1) lon4 = lon_in(i, jp1); lat4 = lat_in(i, jp1) if(lon .lt. lon_min .or. lon .gt. lon_max) then if(i .ne. nlon_in) then cycle else if(lon .lt. lon_min) then lon1 = lon1 -tpi; lon4 = lon4 - tpi else if(lon .gt. lon_max) then lon2 = lon2 +tpi; lon3 = lon3 + tpi endif endif endif if(lat .ge. intersect(lon1,lat1,lon2,lat2,lon))then ! south if(lon .le. intersect(lat2,lon2,lat3,lon3,lat))then ! east if(lat .le. intersect(lon3,lat3,lon4,lat4,lon))then ! north if(lon .ge. intersect(lat4,lon4,lat1,lon1,lat))then ! west found = .true. Interp % i_lon (m,n,1) = i; Interp % i_lon (m,n,2) = ip1 Interp % j_lat (m,n,1) = j; Interp % j_lat (m,n,2) = jp1 exit J_LOOP endif endif endif endif enddo enddo J_LOOP else step = 0 do while ( .not. found .and. step .lt. max_step ) !--- take the adajcent point as the starting point if(m == 1) then is = Interp % i_lon (m,n-1,1) js = Interp % j_lat (m,n-1,1) else is = Interp % i_lon (m-1,n,1) js = Interp % j_lat (m-1,n,1) endif if(step==0) then npts = 1 ilon(1) = is jlat(1) = js else npts = 0 !--- bottom boundary jstart = max(js-step,1) jend = min(js+step,nlat_in) do l = -step, step i = is+l if(src_modulo)then if( i < 1) then i = i + nlon_in else if (i > nlon_in) then i = i - nlon_in endif if( i < 1 .or. i > nlon_in) call mpp_error(FATAL, & 'horiz_interp_bilinear_mod: max_step is too big, decrease max_step' ) else if( i < 1 .or. i > nlon_in) cycle endif npts = npts + 1 ilon(npts) = i jlat(npts) = jstart enddo !--- right and left boundary ----------------------------------------------- istart = is - step iend = is + step if(src_modulo) then if( istart < 1) istart = istart + nlon_in if( iend > nlon_in) iend = iend - nlon_in else istart = max(istart,1) iend = min(iend, nlon_in) endif do l = -step, step j = js+l if( j < 1 .or. j > nlat_in .or. j==jstart .or. j==jend) cycle npts = npts+1 ilon(npts) = istart jlat(npts) = j npts = npts+1 ilon(npts) = iend jlat(npts) = j end do !--- top boundary do l = -step, step i = is+l if(src_modulo)then if( i < 1) then i = i + nlon_in else if (i > nlon_in) then i = i - nlon_in endif if( i < 1 .or. i > nlon_in) call mpp_error(FATAL, & 'horiz_interp_bilinear_mod: max_step is too big, decrease max_step' ) else if( i < 1 .or. i > nlon_in) cycle endif npts = npts + 1 ilon(npts) = i jlat(npts) = jend enddo end if !--- find the surrouding points do l = 1, npts i = ilon(l) j = jlat(l) ip1 = i+1 if(ip1>nlon_in) then if(src_modulo) then ip1 = 1 else cycle endif endif jp1 = j+1 if(jp1>nlat_in) cycle lon1 = lon_in(i, j); lat1 = lat_in(i,j) lon2 = lon_in(ip1,j); lat2 = lat_in(ip1,j) lon3 = lon_in(ip1,jp1); lat3 = lat_in(ip1,jp1) lon4 = lon_in(i, jp1); lat4 = lat_in(i, jp1) if(lon .lt. lon_min .or. lon .gt. lon_max) then if(i .ne. nlon_in) then cycle else if(lon .lt. lon_min) then lon1 = lon1 -tpi; lon4 = lon4 - tpi else if(lon .gt. lon_max) then lon2 = lon2 +tpi; lon3 = lon3 + tpi endif endif endif if(lat .ge. intersect(lon1,lat1,lon2,lat2,lon))then ! south if(lon .le. intersect(lat2,lon2,lat3,lon3,lat))then ! east if(lat .le. intersect(lon3,lat3,lon4,lat4,lon))then !north if(lon .ge. intersect(lat4,lon4,lat1,lon1,lat))then ! west found = .true. is=i; js=j Interp % i_lon (m,n,1) = i; Interp % i_lon (m,n,2) = ip1 Interp % j_lat (m,n,1) = j; Interp % j_lat (m,n,2) = jp1 exit endif endif endif endif enddo step = step + 1 enddo endif if(.not.found) then print *,'lon,lat=',lon*180./PI,lat*180./PI print *,'npts=',npts print *,'is,ie= ',istart,iend print *,'js,je= ',jstart,jend print *,'lon_in(is,js)=',lon_in(istart,jstart)*180./PI print *,'lon_in(ie,js)=',lon_in(iend,jstart)*180./PI print *,'lat_in(is,js)=',lat_in(istart,jstart)*180./PI print *,'lat_in(ie,js)=',lat_in(iend,jstart)*180./PI print *,'lon_in(is,je)=',lon_in(istart,jend)*180./PI print *,'lon_in(ie,je)=',lon_in(iend,jend)*180./PI print *,'lat_in(is,je)=',lat_in(istart,jend)*180./PI print *,'lat_in(ie,je)=',lat_in(iend,jend)*180./PI call mpp_error(FATAL, & 'find_neighbor: the destination point is not inside the source grid' ) endif enddo enddo end subroutine find_neighbor !####################################################################### ! ! The function will return true if the point x,y is inside a polygon, or ! NO if it is not. If the point is exactly on the edge of a polygon, ! the function will return .true. ! ! real polyx(:) : longitude coordinates of corners ! real polyx(:) : latitude coordinates of corners ! real x,y : point to be tested ! ??? How to deal with truncation error. ! function inside_polygon(polyx, polyy, x, y) real, dimension(:), intent(in) :: polyx, polyy real, intent(in) :: x, y logical :: inside_polygon integer :: i, j, nedges real :: xx inside_polygon = .false. nedges = size(polyx(:)) j = nedges do i = 1, nedges if( (polyy(i) < y .AND. polyy(j) >= y) .OR. (polyy(j) < y .AND. polyy(i) >= y) ) then xx = polyx(i)+(y-polyy(i))/(polyy(j)-polyy(i))*(polyx(j)-polyx(i)) if( xx == x ) then inside_polygon = .true. return else if( xx < x ) then inside_polygon = .not. inside_polygon endif endif j = i enddo return end function inside_polygon !####################################################################### ! this routine will search the source grid to fine the grid box that encloses ! each destination grid. subroutine find_neighbor_new( Interp, lon_in, lat_in, lon_out, lat_out, src_modulo, no_crash ) type(horiz_interp_type), intent(inout) :: Interp real, intent(in), dimension(:,:) :: lon_in , lat_in real, intent(in), dimension(:,:) :: lon_out, lat_out logical, intent(in) :: src_modulo, no_crash integer :: nlon_in, nlat_in, nlon_out, nlat_out integer :: max_step, n, m, l, i, j, ip1, jp1, step integer :: is, js, jstart, jend, istart, iend, npts integer, allocatable, dimension(:) :: ilon, jlat real :: lon_min, lon_max, lon, lat, tpi logical :: found real :: polyx(4), polyy(4) real :: min_lon, min_lat, max_lon, max_lat integer, parameter :: step_div=8 tpi = 2.0*pi nlon_in = size(lon_in,1) ; nlat_in = size(lat_in,2) nlon_out = size(lon_out,1); nlat_out = size(lon_out,2) lon_min = minval(lon_in); lon_max = maxval(lon_in); max_step = min(nlon_in,nlat_in)/step_div ! can be adjusted if needed allocate(ilon(step_div*max_step), jlat(step_div*max_step) ) do n = 1, nlat_out do m = 1, nlon_out found = .false. lon = lon_out(m,n) lat = lat_out(m,n) if(src_modulo) then if(lon .lt. lon_min) then lon = lon + tpi else if(lon .gt. lon_max) then lon = lon - tpi endif else if(lon .lt. lon_min .or. lon .gt. lon_max ) & call mpp_error(FATAL,'horiz_interp_bilinear_mod: ' //& 'when input grid is not modulo, output grid should locate inside input grid') endif !--- search for the surrounding four points locatioon. if(m==1 .and. n==1) then J_LOOP: do j = 1, nlat_in-1 do i = 1, nlon_in ip1 = i+1 jp1 = j+1 if(i==nlon_in) then if(src_modulo)then ip1 = 1 else cycle endif endif polyx(1) = lon_in(i, j); polyy(1) = lat_in(i,j) polyx(2) = lon_in(ip1,j); polyy(2) = lat_in(ip1,j) polyx(3) = lon_in(ip1,jp1); polyy(3) = lat_in(ip1,jp1) polyx(4) = lon_in(i, jp1); polyy(4) = lat_in(i, jp1) if(lon .lt. lon_min .or. lon .gt. lon_max) then if(i .ne. nlon_in) then cycle else if(lon .lt. lon_min) then polyx(1) = polyx(1) -tpi; polyx(4) = polyx(4) - tpi else if(lon .gt. lon_max) then polyx(2) = polyx(2) +tpi; polyx(3) = polyx(3) + tpi endif endif endif min_lon = minval(polyx) max_lon = maxval(polyx) min_lat = minval(polyy) max_lat = maxval(polyy) ! if( lon .GE. min_lon .AND. lon .LE. max_lon .AND. & ! lat .GE. min_lat .AND. lat .LE. max_lat ) then ! print*, 'i =', i, 'j = ', j ! print '(5f15.11)', lon, polyx ! print '(5f15.11)', lat, polyy ! endif if(inside_polygon(polyx, polyy, lon, lat)) then found = .true. ! print*, " found ", i, j Interp % i_lon (m,n,1) = i; Interp % i_lon (m,n,2) = ip1 Interp % j_lat (m,n,1) = j; Interp % j_lat (m,n,2) = jp1 exit J_LOOP endif enddo enddo J_LOOP else step = 0 do while ( .not. found .and. step .lt. max_step ) !--- take the adajcent point as the starting point if(m == 1) then is = Interp % i_lon (m,n-1,1) js = Interp % j_lat (m,n-1,1) else is = Interp % i_lon (m-1,n,1) js = Interp % j_lat (m-1,n,1) endif if(step==0) then npts = 1 ilon(1) = is jlat(1) = js else npts = 0 !--- bottom and top boundary jstart = max(js-step,1) jend = min(js+step,nlat_in) do l = -step, step i = is+l if(src_modulo)then if( i < 1) then i = i + nlon_in else if (i > nlon_in) then i = i - nlon_in endif if( i < 1 .or. i > nlon_in) call mpp_error(FATAL, & 'horiz_interp_bilinear_mod: max_step is too big, decrease max_step' ) else if( i < 1 .or. i > nlon_in) cycle endif npts = npts + 1 ilon(npts) = i jlat(npts) = jstart npts = npts + 1 ilon(npts) = i jlat(npts) = jend enddo !--- right and left boundary ----------------------------------------------- istart = is - step iend = is + step if(src_modulo) then if( istart < 1) istart = istart + nlon_in if( iend > nlon_in) iend = iend - nlon_in else istart = max(istart,1) iend = min(iend, nlon_in) endif do l = -step, step j = js+l if( j < 1 .or. j > nlat_in) cycle npts = npts+1 ilon(npts) = istart jlat(npts) = j npts = npts+1 ilon(npts) = iend jlat(npts) = j end do end if !--- find the surrouding points do l = 1, npts i = ilon(l) j = jlat(l) ip1 = i+1 if(ip1>nlon_in) then if(src_modulo) then ip1 = 1 else cycle endif endif jp1 = j+1 if(jp1>nlat_in) cycle polyx(1) = lon_in(i, j); polyy(1) = lat_in(i,j) polyx(2) = lon_in(ip1,j); polyy(2) = lat_in(ip1,j) polyx(3) = lon_in(ip1,jp1); polyy(3) = lat_in(ip1,jp1) polyx(4) = lon_in(i, jp1); polyy(4) = lat_in(i, jp1) if(inside_polygon(polyx, polyy, lon, lat)) then found = .true. Interp % i_lon (m,n,1) = i; Interp % i_lon (m,n,2) = ip1 Interp % j_lat (m,n,1) = j; Interp % j_lat (m,n,2) = jp1 exit endif enddo step = step + 1 enddo endif if(.not.found) then if(no_crash) then Interp % i_lon (m,n,1:2) = DUMMY Interp % j_lat (m,n,1:2) = DUMMY print*,'lon,lat=',lon,lat ! snz else call mpp_error(FATAL, & 'horiz_interp_bilinear_mod: the destination point is not inside the source grid' ) endif endif enddo enddo end subroutine find_neighbor_new !####################################################################### function intersect(x1, y1, x2, y2, x) real, intent(in) :: x1, y1, x2, y2, x real :: intersect intersect = (y2-y1)*(x-x1)/(x2-x1) + y1 return end function intersect !####################################################################### ! ! ! Subroutine for performing the horizontal interpolation between two grids. ! ! ! Subroutine for performing the horizontal interpolation between two grids. ! horiz_interp_bilinear_new must be called before calling this routine. ! ! ! ! ! Derived-type variable containing interpolation indices and weights. ! Returned by a previous call to horiz_interp_bilinear_new. ! ! ! Input data on source grid. ! ! ! flag for the amount of print output. ! verbose = 0, no output; = 1, min,max,means; = 2, still more ! ! ! Input mask, must be the same size as the input data. The real value of ! mask_in must be in the range (0.,1.). Set mask_in=0.0 for data points ! that should not be used or have missing data. ! ! ! Use the missing_value to indicate missing data. ! ! ! numbers of points allowed to miss for the bilinear interpolation. The value ! should be between 0 and 3. ! ! ! Output data on destination grid. ! ! ! Output mask that specifies whether data was computed. ! subroutine horiz_interp_bilinear ( Interp, data_in, data_out, verbose, mask_in,mask_out, & missing_value, missing_permit, new_handle_missing ) !----------------------------------------------------------------------- type (horiz_interp_type), intent(in) :: Interp real, intent(in), dimension(:,:) :: data_in real, intent(out), dimension(:,:) :: data_out integer, intent(in), optional :: verbose real, intent(in), dimension(:,:), optional :: mask_in real, intent(out), dimension(:,:), optional :: mask_out real, intent(in), optional :: missing_value integer, intent(in), optional :: missing_permit logical, intent(in), optional :: new_handle_missing !----------------------------------------------------------------------- integer :: nlon_in, nlat_in, nlon_out, nlat_out, n, m, & is, ie, js, je, iverbose, max_missing, num_missing, & miss_in, miss_out, unit real :: dwtsum, wtsum, min_in, max_in, avg_in, & min_out, max_out, avg_out, wtw, wte, wts, wtn real :: mask(size(data_in,1), size(data_in,2) ) logical :: set_to_missing, is_missing(4), new_handler real :: f1, f2, f3, f4, middle, w, s num_missing = 0 nlon_in = Interp%nlon_src; nlat_in = Interp%nlat_src nlon_out = Interp%nlon_dst; nlat_out = Interp%nlat_dst if(present(mask_in)) then mask = mask_in else mask = 1.0 endif if (present(verbose)) then iverbose = verbose else iverbose = 0 endif if(present(missing_permit)) then max_missing = missing_permit else max_missing = 0 endif if(present(new_handle_missing)) then new_handler = new_handle_missing else new_handler = .false. endif if(max_missing .gt. 3 .or. max_missing .lt. 0) call mpp_error(FATAL, & 'horiz_interp_bilinear_mod: missing_permit should be between 0 and 3') if (size(data_in,1) /= nlon_in .or. size(data_in,2) /= nlat_in) & call mpp_error(FATAL,'horiz_interp_bilinear_mod: size of input array incorrect') if (size(data_out,1) /= nlon_out .or. size(data_out,2) /= nlat_out) & call mpp_error(FATAL,'horiz_interp_bilinear_mod: size of output array incorrect') if(new_handler) then if( .not. present(missing_value) ) call mpp_error(FATAL, & "horiz_interp_bilinear_mod: misisng_value must be present when new_handle_missing is .true.") if( present(mask_in) ) call mpp_error(FATAL, & "horiz_interp_bilinear_mod: mask_in should not be present when new_handle_missing is .true.") do n = 1, nlat_out do m = 1, nlon_out is = Interp % i_lon (m,n,1); ie = Interp % i_lon (m,n,2) js = Interp % j_lat (m,n,1); je = Interp % j_lat (m,n,2) wtw = Interp % wti (m,n,1) wte = Interp % wti (m,n,2) wts = Interp % wtj (m,n,1) wtn = Interp % wtj (m,n,2) is_missing = .false. num_missing = 0 set_to_missing = .false. if(data_in(is,js) == missing_value) then num_missing = num_missing+1 is_missing(1) = .true. if(wtw .GE. 0.5 .AND. wts .GE. 0.5) set_to_missing = .true. endif if(data_in(ie,js) == missing_value) then num_missing = num_missing+1 is_missing(2) = .true. if(wte .GE. 0.5 .AND. wts .GE. 0.5) set_to_missing = .true. endif if(data_in(ie,je) == missing_value) then num_missing = num_missing+1 is_missing(3) = .true. if(wte .GE. 0.5 .AND. wtn .GE. 0.5) set_to_missing = .true. endif if(data_in(is,je) == missing_value) then num_missing = num_missing+1 is_missing(4) = .true. if(wtw .GE. 0.5 .AND. wtn .GE. 0.5) set_to_missing = .true. endif if( num_missing == 4 .OR. set_to_missing ) then data_out(m,n) = missing_value if(present(mask_out)) mask_out(m,n) = 0.0 cycle else if(num_missing == 0) then f1 = data_in(is,js) f2 = data_in(ie,js) f3 = data_in(ie,je) f4 = data_in(is,je) w = wtw s = wts else if(num_missing == 3) then !--- three missing value if(.not. is_missing(1) ) then data_out(m,n) = data_in(is,js) else if(.not. is_missing(2) ) then data_out(m,n) = data_in(ie,js) else if(.not. is_missing(3) ) then data_out(m,n) = data_in(ie,je) else if(.not. is_missing(4) ) then data_out(m,n) = data_in(is,je) endif if(present(mask_out) ) mask_out(m,n) = 1.0 cycle else !--- one or two missing value if( num_missing == 1) then if( is_missing(1) .OR. is_missing(3) ) then middle = 0.5*(data_in(ie,js)+data_in(is,je)) else middle = 0.5*(data_in(is,js)+data_in(ie,je)) endif else ! num_missing = 2 if( is_missing(1) .AND. is_missing(2) ) then middle = 0.5*(data_in(ie,je)+data_in(is,je)) else if( is_missing(1) .AND. is_missing(3) ) then middle = 0.5*(data_in(ie,js)+data_in(is,je)) else if( is_missing(1) .AND. is_missing(4) ) then middle = 0.5*(data_in(ie,js)+data_in(ie,je)) else if( is_missing(2) .AND. is_missing(3) ) then middle = 0.5*(data_in(is,js)+data_in(is,je)) else if( is_missing(2) .AND. is_missing(4) ) then middle = 0.5*(data_in(is,js)+data_in(ie,je)) else if( is_missing(3) .AND. is_missing(4) ) then middle = 0.5*(data_in(is,js)+data_in(ie,js)) endif endif if( wtw .GE. 0.5 .AND. wts .GE. 0.5 ) then ! zone 1 w = 2.0*(wtw-0.5) s = 2.0*(wts-0.5) f1 = data_in(is,js) if(is_missing(2)) then f2 = f1 else f2 = 0.5*(data_in(is,js)+data_in(ie,js)) endif f3 = middle if(is_missing(4)) then f4 = f1 else f4 = 0.5*(data_in(is,js)+data_in(is,je)) endif else if( wte .GE. 0.5 .AND. wts .GE. 0.5 ) then ! zone 2 w = 2.0*(1.0-wte) s = 2.0*(wts-0.5) f2 = data_in(ie,js) if(is_missing(1)) then f1 = f2 else f1 = 0.5*(data_in(is,js)+data_in(ie,js)) endif f4 = middle if(is_missing(3)) then f3 = f2 else f3 = 0.5*(data_in(ie,js)+data_in(ie,je)) endif else if( wte .GE. 0.5 .AND. wtn .GE. 0.5 ) then ! zone 3 w = 2.0*(1.0-wte) s = 2.0*(1.0-wtn) f3 = data_in(ie,je) if(is_missing(2)) then f2 = f3 else f2 = 0.5*(data_in(ie,js)+data_in(ie,je)) endif f1 = middle if(is_missing(4)) then f4 = f3 else f4 = 0.5*(data_in(ie,je)+data_in(is,je)) endif else if( wtw .GE. 0.5 .AND. wtn .GE. 0.5 ) then ! zone 4 w = 2.0*(wtw-0.5) s = 2.0*(1.0-wtn) f4 = data_in(is,je) if(is_missing(1)) then f1 = f4 else f1 = 0.5*(data_in(is,js)+data_in(is,je)) endif f2 = middle if(is_missing(3)) then f3 = f4 else f3 = 0.5*(data_in(ie,je)+data_in(is,je)) endif else call mpp_error(FATAL, & "horiz_interp_bilinear_mod: the point should be in one of the four zone") endif endif data_out(m,n) = f3 + (f4-f3)*w + (f2-f3)*s + ((f1-f2)+(f3-f4))*w*s if(present(mask_out)) mask_out(m,n) = 1.0 enddo enddo else do n = 1, nlat_out do m = 1, nlon_out is = Interp % i_lon (m,n,1); ie = Interp % i_lon (m,n,2) js = Interp % j_lat (m,n,1); je = Interp % j_lat (m,n,2) wtw = Interp % wti (m,n,1) wte = Interp % wti (m,n,2) wts = Interp % wtj (m,n,1) wtn = Interp % wtj (m,n,2) if(present(missing_value) ) then num_missing = 0 if(data_in(is,js) == missing_value) then num_missing = num_missing+1 mask(is,js) = 0.0 endif if(data_in(ie,js) == missing_value) then num_missing = num_missing+1 mask(ie,js) = 0.0 endif if(data_in(ie,je) == missing_value) then num_missing = num_missing+1 mask(ie,je) = 0.0 endif if(data_in(is,je) == missing_value) then num_missing = num_missing+1 mask(is,je) = 0.0 endif endif dwtsum = data_in(is,js)*mask(is,js)*wtw*wts & + data_in(ie,js)*mask(ie,js)*wte*wts & + data_in(ie,je)*mask(ie,je)*wte*wtn & + data_in(is,je)*mask(is,je)*wtw*wtn wtsum = mask(is,js)*wtw*wts + mask(ie,js)*wte*wts & + mask(ie,je)*wte*wtn + mask(is,je)*wtw*wtn if(.not. present(mask_in) .and. .not. present(missing_value)) wtsum = 1.0 if(num_missing .gt. max_missing ) then data_out(m,n) = missing_value if(present(mask_out)) mask_out(m,n) = 0.0 else if(wtsum .lt. epsln) then if(present(missing_value)) then data_out(m,n) = missing_value else data_out(m,n) = 0.0 endif if(present(mask_out)) mask_out(m,n) = 0.0 else data_out(m,n) = dwtsum/wtsum if(present(mask_out)) mask_out(m,n) = wtsum endif enddo enddo endif !*********************************************************************** ! compute statistics: minimum, maximum, and mean !----------------------------------------------------------------------- if (iverbose > 0) then ! compute statistics of input data call stats (data_in, min_in, max_in, avg_in, miss_in, missing_value, mask_in) ! compute statistics of output data call stats (data_out, min_out, max_out, avg_out, miss_out, missing_value, mask_out) !---- output statistics ---- unit = stdout() write (unit,900) write (unit,901) min_in ,max_in, avg_in if (present(mask_in)) write (unit,903) miss_in write (unit,902) min_out,max_out,avg_out if (present(mask_out)) write (unit,903) miss_out 900 format (/,1x,10('-'),' output from horiz_interp ',10('-')) 901 format (' input: min=',f16.9,' max=',f16.9,' avg=',f22.15) 902 format (' output: min=',f16.9,' max=',f16.9,' avg=',f22.15) 903 format (' number of missing points = ',i6) endif return end subroutine horiz_interp_bilinear ! !####################################################################### ! ! ! Deallocates memory used by "horiz_interp_type" variables. ! Must be called before reinitializing with horiz_interp_bilinear_new. ! ! ! Deallocates memory used by "horiz_interp_type" variables. ! Must be called before reinitializing with horiz_interp_bilinear_new. ! ! ! ! A derived-type variable returned by previous call ! to horiz_interp_bilinear_new. The input variable must have ! allocated arrays. The returned variable will contain ! deallocated arrays. ! subroutine horiz_interp_bilinear_del( Interp ) type (horiz_interp_type), intent(inout) :: Interp if(associated(Interp%wti)) deallocate(Interp%wti) if(associated(Interp%wtj)) deallocate(Interp%wtj) if(associated(Interp%i_lon)) deallocate(Interp%i_lon) if(associated(Interp%j_lat)) deallocate(Interp%j_lat) end subroutine horiz_interp_bilinear_del ! !####################################################################### function indp (value, array) integer :: indp real, dimension(:), intent(in) :: array real, intent(in) :: value ! !======================================================================= ! ! indp = index of nearest data point within "array" corresponding to ! "value". ! inputs: ! value = arbitrary data...same units as elements in "array" ! array = array of data points (must be monotonically increasing) ! output: ! indp = index of nearest data point to "value" ! if "value" is outside the domain of "array" then indp = 1 ! or "ia" depending on whether array(1) or array(ia) is ! closest to "value" !======================================================================= ! integer i, ia, unit logical keep_going ! ia = size(array(:)) do i=2,ia if (array(i) .lt. array(i-1)) then unit = stdout() write (unit,*) & ' => Error: array must be monotonically increasing in "indp"' , & ' when searching for nearest element to value=',value write (unit,*) ' array(i) < array(i-1) for i=',i write (unit,*) ' array(i) for i=1..ia follows:' call abort() endif enddo if (value .lt. array(1) .or. value .gt. array(ia)) then if (value .lt. array(1)) indp = 1 if (value .gt. array(ia)) indp = ia else i=1 keep_going = .true. do while (i .le. ia .and. keep_going) i = i+1 if (value .le. array(i)) then indp = i if (array(i)-value .gt. value-array(i-1)) indp = i-1 keep_going = .false. endif enddo endif return end function indp !###################################################################### end module horiz_interp_bilinear_mod