!------------------------------------------------------------------------------ ! ! MODULE: GridTemplate ! ! DESCRIPTION: !> Provide subroutines to calculate nearby gridpoints for station observations !> Must use the following steps to achieve the result: !> 1. call initNearbyGridPoints !> 2. call getNearbyGridPoints !> 3. call doneNearbyGridPoints !> It also provides subroutine freeNearbyGridPoints to free the memory allocated !> for a nearby_gridpoint_t object !> !> Other independant subroutines: !> zoominGDS !> convertProjection ! ! REVISION HISTORY: ! September 2011 ! January 2014 - modified ! !------------------------------------------------------------------------------ module GridTemplate use Kinds use GDSWZD_MOD private public nearby_gridpoint_t ! The required 3 steps for nearby gridpoints public initNearbyGridPoints, getNearbyGridPoints, doneNearbyGridPoints ! Help to release the memory of the gridpoints public freeNearbyGridPoints ! ! Other auxiliary subroutines for Radar and Satellite public zoominGDS, convertProjection ! grid points for METAR, SHIPs, PIREPs and LIGHTNING ! Data structure: stack ! : %next==NULL means empty, the first element is useless. type :: nearby_gridpoint_t integer :: i integer :: j real :: distance type(nearby_gridpoint_t), pointer :: next end type nearby_gridpoint_t real, parameter :: RERTH = 6371.2040 ! in km real :: radius integer :: kgds(200) integer :: nx, ny, projType ! real, allocatable :: lat(:), lon(:) ! for GFS Guassian, in degree real :: dx, dy ! for RAP/NAM Lambert Conformal, in km real, allocatable :: alat(:) ! expanded version of lat contains !---------------------------------------------------------------------------- ! DESCRIPTION: !> Assign/calculate member variables from input KGDS and influencing radius. !> Meanwhile, allocate memory of lat(:) lon(:) if it's a GFS model ! !> @param[in] kgds0 - model grid information !> @param[in] radius0 - radius within which an observation influences !---------------------------------------------------------------------------- subroutine initNearbyGridPoints(kgds0, radius0) implicit none integer, intent(in) :: kgds0(200) real, intent(in) :: radius0 real, allocatable :: slat(:), mesh(:) ! For GFS Guassian integer :: i kgds(:) = kgds0(:) radius = radius0 nx = kgds(2) ny = kgds(3) projType = kgds(1) ! LAMBERT CONFORMAL if(projType == 3) then dx = kgds(8)/1000. ! in km dy = kgds(9)/1000. ! in km else if(projType == 4 .or. projType == 0) then ! if GAUSSIAN CYLINDRICAL or EQUIDISTANT CYLINDRICAL allocate(mesh(ny)) allocate( lat(ny)) allocate( lon(nx)) ! caculate lat lon lon(:) = (/ ((360./nx) * (i-1), i = 1, nx) /) ! in degree call SPLAT(projType, ny, lat(:), mesh(:)) ! sine of latitude lat(:) = asin(lat(:)) * R2D ! in degree ! deallocate mesh deallocate(mesh) end if allocate(alat(0:ny+1)) alat(1:ny) = lat(1:ny) alat(0)=90.0 alat(ny+1)=-90.0 return end subroutine initNearbyGridPoints !---------------------------------------------------------------------------- ! DESCRIPTION: !> Get all grid points (i,j)s within the radius from the station (lat0, lon0) !> for GFS / (x0, y0) for RAP/NAM !> These gridpoints are influenced by the station locations. ! !> @param[in] lat0 - latitude !> @param[in] lon0 - logitude !> @param[out] points - nearby gridpoint stack !---------------------------------------------------------------------------- subroutine getNearbyGridPoints(lat0, lon0, points) implicit none real, intent(inout) :: lat0, lon0 ! (lat, lon) of a station ! gridpoints influenced by the station type(nearby_gridpoint_t), target, intent(out) :: points ! To call GDSWZD() integer :: iopt, npts, ret ! rlon [-360, 360], rlat [-90, 90] real :: fill,x0, y0,crot0,srot0 real, dimension(1) :: aX,aY,aLat,aLon,aCrot,aSrot ! To include unique gridpoints in all quadrants type :: ij_direction_t integer :: start integer :: end integer :: step end type ij_direction_t type(ij_direction_t) :: idirection(2), jdirection(2) integer :: i, j, ii, jj, idir, jdir real :: distance nullify(points%next) ! To avoid duplicate gridpoints at high lat of GFS, which are closed to each other idirection(1) = ij_direction_t( 0, nx/2, 1) idirection(2) = ij_direction_t(-1,-(nx-1)/2, -1) jdirection(1) = ij_direction_t( 0, ny, 1) jdirection(2) = ij_direction_t(-1,-ny, -1) npts = 1 ! 1 point to be calculated aLat(1)=lat0 aLon(1)=lon0 fill = -1. ! for invalid value iopt = -1 ! COMPUTE GRID COORDS OF SELECTED EARTH COORDS !iopt = 1 ! COMPUTE EARTH COORDS OF SELECTED GRID COORDS !iopt = 0 ! COMPUTE EARTH COORDS OF ALL THE GRID POINTS ! call GDSWZD modified version to avoid calling SPLAT, ! scalar value argument is used. if (kgds(1) == 4) then call m_gdswzd04(kgds,iopt,npts,fill,x0,y0,lon0,lat0,ret,crot0,srot0) else ! call GDSWZD(kgds,iopt,npts,fill,x0,y0,lon0,lat0,ret,crot,srot) call GDSWZD(kgds,iopt,npts,fill,aX,aY,aLon,aLat,ret,aCrot,aSrot) x0 = aX(1) y0 = aY(1) endif ! with radious==0.0, only add one grid point nearest to (lat0, lon0) if( radius < 0.0001) then i = nint(x0) j = nint(y0) if( (i >= 1 .and. i <= nx) .and. (j >= 1 .and. j <= ny)) then call m_addNearbyGridPt(i, j, radius, points) end if return end if ! LAMBERT CONFORMAL if_projType: if(projType == 3) then if_lc_y0: if( y0 >= 0.) then ! discard stations out of model's domain ! control quadrants by x/y axes direction do jdir = 1, 2 do idir = 1, 2 ! control searching range from nearest to fartherest, stop promptly do jj = jdirection(jdir)%start, jdirection(jdir)%end, jdirection(jdir)%step do ii = idirection(idir)%start, idirection(idir)%end, idirection(idir)%step ! For NAM/RAP Lambert, distance between gridpoints are quadrantly same distance = sqrt(((ii-x0)*dx)**2 + ((jj-y0)*dy)**2) if_distance_lc: if( distance <= radius) then i = nint(x0 + ii) j = nint(y0 + jj) if( (i >= 1 .and. i <= nx) .and. (j >= 1 .and. j <= ny)) then !write(*,*) x0, y0, ii, jj, distance call m_addNearbyGridPt(i, j, distance, points) endif else exit endif if_distance_lc enddo enddo enddo enddo endif if_lc_y0 ! GAUSSIAN CYLINDRICAL / EQUIDISTANT CYLINDRICAL elseif(projType == 4 .or. projType == 0) then if_gc_y0: if( y0 >= 0.) then ! I don't see the reason for GFS, but to be safe ! control quadrants by x/y axes direction do jdir = 1, 2 do idir = 1, 2 ! control searching range from nearest to fartherest, stop promptly do jj = jdirection(jdir)%start, jdirection(jdir)%end, jdirection(jdir)%step j = nint(y0 + jj) if(j < 1 .or. j > ny) exit do ii = idirection(idir)%start, idirection(idir)%end, idirection(idir)%step i = nint(x0 + ii) if(i < 1) i = i + nx if(i > nx) i = i - nx ! For GFS Guassian, distance between grid points are south-north differently distance = m_distanceLatLon(lat0, lon0, lat(j), lon(i)) if_distance_gc: if( distance <= radius) then !write(*,*) x0, y0, ii, jj, distance call m_addNearbyGridPt(i, j, distance, points) else exit endif if_distance_gc enddo enddo enddo enddo endif if_gc_y0 else write(*,*) "Projection kgpds(1)=", projType, " is not supported yet" return endif if_projType return end subroutine getNearbyGridPoints !---------------------------------------------------------------------------- ! DESCRIPTION: !> Mimic gdswiz04.f from IPLIB, to skip SPLAT call for each observation. !> This subroutine only calcuate scalar value, not an array !> This subroutine depends on lat(:) initialized by initNearbyGridPoints() ! !---------------------------------------------------------------------------- subroutine m_gdswzd04(kgds,iopt,npts,fill,xpts,ypts,rlon,rlat,nret,crot,srot) implicit none integer, intent(in) :: kgds(200) integer, intent(in) :: iopt, npts real, intent(in) :: fill real :: xpts,ypts! input if iopt>=0; output if iopt<0 real :: rlon,rlat! output if iopt>=0; input if iopt<0 integer, intent(out) :: nret real, intent(out) :: crot,srot integer :: im,jm,jg,iscan,jscan,nscan real :: rlat1,rlat2,rlon1,rlon2,dlon integer :: jh,j1,j2 real :: hi,xmin,xmax,ymin,ymax integer :: j,n,ja real :: wb,rlata,rlatb,yptsa,yptsb crot=1 srot=0 nret=-1 if(npts > 1) then write(*,*) "m_gdswzd04() only calculates scalar value, not an array" if(iopt>=0) then rlon=fill rlat=fill endif if(iopt<=0) then xpts=fill ypts=fill endif return endif if(kgds(1) == 4) then im=kgds(2) jm=kgds(3) rlat1=kgds(4)*1.e-3 rlon1=kgds(5)*1.e-3 rlat2=kgds(7)*1.e-3 rlon2=kgds(8)*1.e-3 jg=kgds(10)*2 iscan=mod(kgds(11)/128,2) jscan=mod(kgds(11)/64,2) nscan=mod(kgds(11)/32,2) hi=(-1.)**iscan jh=(-1)**jscan dlon=hi*(mod(hi*(rlon2-rlon1)-1+3600,360.)+1)/(im-1) j1=1 do while(j1<jg .and. rlat1<(alat(j1)+alat(j1+1))/2) j1=j1+1 enddo j2=j1+jh*(jm-1) xmin=0 xmax=im+1 if(im == nint(360./abs(dlon))) xmax=im+2 ymin=0.5 ymax=jm+0.5 nret=0 ! translate grid coordinates to earth coordinates if(iopt == 0 .or. iopt == 1) then if(xpts >= xmin .and. xpts <= xmax.and. & ypts >= ymin .and. ypts <= ymax) then rlon=mod(rlon1+dlon*(xpts-1)+3600,360.) j=min(int(ypts),jm) rlata=alat(j1+jh*(j-1)) rlatb=alat(j1+jh*j) wb=ypts-j rlat=rlata+wb*(rlatb-rlata) nret=nret+1 else rlon=fill rlat=fill endif ! translate earth coordinates to grid coordinates elseif(iopt == -1) then xpts=fill ypts=fill if(abs(rlon)<=360 .and. abs(rlat) <= 90) then xpts=1+hi*mod(hi*(rlon-rlon1)+3600,360.)/dlon ja=min(int((jg+1)/180.*(90-rlat)),jg) if(rlat>alat(ja)) ja=max(ja-2,0) if(rlat<alat(ja+1)) ja=min(ja+2,jg) if(rlat>alat(ja)) ja=ja-1 if(rlat<alat(ja+1)) ja=ja+1 yptsa=1+jh*(ja-j1) yptsb=1+jh*(ja+1-j1) wb=(alat(ja)-rlat)/(alat(ja)-alat(ja+1)) ypts=yptsa+wb*(yptsb-yptsa) if(xpts>=xmin.and.xpts<=xmax .and. & ypts>=ymin.and.ypts<=ymax) then nret=nret+1 else xpts=fill ypts=fill endif endif endif ! projection unrecognized else if(iopt>=0) then rlon=fill rlat=fill endif if(iopt<=0) then xpts=fill ypts=fill endif endif return end subroutine m_gdswzd04 !---------------------------------------------------------------------------- ! DESCRIPTION: !> Add a grid point (i,j) with distance to nearby gridpoint stack ! !> @param[in] i - coordinate i of a gridpoint !> @param[in] j - coordinate j of a gridpoint !> @param[in] distance - distance between (lat0, lon0) and gridpoint (i,j) !> @param[out] points - nearby gridpoint stack !---------------------------------------------------------------------------- subroutine m_addNearbyGridPt(i, j, distance, points) integer, intent(in) :: i, j real, intent(in) :: distance type(nearby_gridpoint_t), target, intent(inout) :: points type(nearby_gridpoint_t), pointer :: pointIterator allocate(pointIterator) pointIterator%i = i pointIterator%j = j pointIterator%distance = distance pointIterator%next => points%next points%next => pointIterator return end subroutine m_addNearbyGridPt !---------------------------------------------------------------------------- ! DESCRIPTION: !> calculate the distance between two earth location of (lat, lon) !---------------------------------------------------------------------------- real function m_distanceLatLon(lat01, lon01, lat02, lon02) real, intent(in) :: lat01, lon01, lat02, lon02 ! in degree real :: lat1, lon1, lat2, lon2 real :: dlat, dlon, a, c ! Calculate the real distance ! http://www.movable-type.co.uk/scripts/latlong.html ! a = [sin(^[$B&$^[(Blat/2)]**2 + ! cos(lat1).cos(lat2).[sin(^[$B&$^[(Blong/2)]**2 ! c = 2.atan2(^[$B"e^[(Ba, ^[$B"e^[(B(1^[$B!]^[(Ba)) ! d = R.c lat1 = lat01 * D2R lon1 = lon01 * D2R lat2 = lat02 * D2R lon2 = lon02 * D2R dlat = lat1-lat2 dlon = lon1-lon2 a = sin(dlat/2.) ** 2.0 + cos(lat1)*cos(lat2) * sin(dlon/2.)*sin(dlon/2.) c = 2. * atan2(sqrt(a), sqrt(1-a)) m_distanceLatLon = RERTH * c end function m_distanceLatLon !---------------------------------------------------------------------------- ! DESCRIPTION: !> Free the memory of nearby gridpoint stack !---------------------------------------------------------------------------- subroutine freeNearbyGridPoints(stack) type(nearby_gridpoint_t), target, intent(inout) :: stack type(nearby_gridpoint_t), pointer :: iterator iterator => stack%next do while(associated(iterator)) iterator => iterator%next deallocate(stack%next, stat=iret) stack%next => iterator end do return end subroutine freeNearbyGridPoints !---------------------------------------------------------------------------- ! DESCRIPTION: !> Do clean up after getting all nearby gridpoints !---------------------------------------------------------------------------- subroutine doneNearbyGridPoints() if(allocated(lat)) deallocate(lat) if(allocated(lon)) deallocate(lon) if(allocated(alat)) deallocate(alat) end subroutine doneNearbyGridPoints !---------------------------------------------------------------------------- ! DESCRIPTION: !> Given a kgds and nfiner, get a zoomin gds !---------------------------------------------------------------------------- subroutine zoominGDS(kgds, nfiner, tgds) integer, intent(in) :: kgds(:) integer, intent(in) :: nfiner integer, intent(inout) :: tgds(:) tgds(:) = kgds(:) if(tgds(1) == 0) then ! EQUIDISTANT CYLINDRICAL tgds(2) = kgds(2) * nfiner tgds(3) = kgds(3) * nfiner ! 4 5 7, the same as kgds tgds(9) = real(kgds(9)) / real(nfiner)! won't be used by GDSWZD04 tgds(10)= real(kgds(10))/ real(nfiner)! won't be used by GDSWZD04 tgds(8) = 360*1000-tgds(9) elseif(tgds(1) == 4) then ! GAUSSIAN CYLINDRICAL tgds(2) = kgds(2) * nfiner tgds(3) = kgds(3) * nfiner tgds(4) = m_getGaussianLat0(tgds(3), tgds(1))*1000.0 tgds(7) = -tgds(4) tgds(8) = int(360. * (tgds(2) - 1)/tgds(2) * 1000) tgds(9) = nint(360./tgds(2) * 1000.) ! won't be used by GDSWZD04 tgds(10) = kgds(10) * nfiner else if(tgds(1) == 3) then ! Lambert Conformal tgds(2) = kgds(2) * nfiner tgds(3) = kgds(3) * nfiner end if return end subroutine zoominGDS !---------------------------------------------------------------------------- ! DESCRIPTION: !> get the latitude of the northernmost grid points on Gaussian grid !---------------------------------------------------------------------------- function m_getGaussianLat0(ny, grid) real :: m_getGaussianLat0 integer, intent(in) :: ny integer, intent(in) :: grid real :: mesh(ny), lat(ny) call splat(grid, ny, lat, mesh) ! grid=4: Gaussian grid lat(:) = asin(lat(:)) * R2D m_getGaussianLat0 = lat(1) end function m_getGaussianLat0 !---------------------------------------------------------------------------- ! DESCRIPTION: !> convert data from a source projection (sgds) to a target projection(tgds) ! !> @param[in] sgds - source projection (provided by GDS grid information) !> @param[in] sdata - source data !> @param[out] tgds - target projection (provided by GDS grid information) !> @param[out] tdata - converted data !> @param[out] iret - status; -1 if failure ! !---------------------------------------------------------------------------- subroutine convertProjection(sgds, sdata, tgds, tdata, iret) implicit none integer, intent(in) :: sgds(:) real, intent(in) :: sdata(:,:) integer, intent(in) :: tgds(:) real, intent(inout) :: tdata(:,:) integer, intent(out):: iret integer :: nx, ny integer :: i, j real, allocatable :: lat(:,:), lon(:,:), x(:,:), y(:,:) real :: lon0, lat0, x0, y0, crot0, srot0 integer :: i0, j0 ! other variables for GDSWZD integer :: iopt, npts real, allocatable :: crot(:,:), srot(:,:) real :: fill fill = MISSING nx = tgds(2) ny = tgds(3) ! get earth coordinate (lat, lon) for each grid point of the target tgds allocate(x(nx, ny)) allocate(y(nx, ny)) allocate(lat(nx, ny)) allocate(lon(nx, ny)) allocate(crot(nx, ny)) allocate(srot(nx, ny)) iopt = 0 ! COMPUTE EARTH COORDS OF ALL THE GRID POINTS npts = nx * ny call GDSWZD(tgds,iopt,npts,fill,x,y,lon,lat,iret,crot,srot) write(*,*) "convertProjection: get target (lat,lon), numbers:", iret iopt = -1 ! COMPUTE GRID COORDS OF SELECTED EARTH COORDS call GDSWZD(sgds,iopt,npts,fill,x,y,lon,lat,iret,crot,srot) write(*,*) "convertProjection: convert target (lat,lon) to source (x,y), numbers:", iret tdata(:,:) = MISSING do j = 1, ny do i = 1, nx i0 = nint(x(i,j)) j0 = nint(y(i,j)) if( (i0 >= 1 .and. i0 <= sgds(2)) .and. (j0 >= 1 .and. j0 <= sgds(3))) then tdata(i,j) = sdata(i0,j0) end if end do end do deallocate(x) deallocate(y) deallocate(crot) deallocate(srot) deallocate(lat) deallocate(lon) iret = 0 return end subroutine convertProjection end module GridTemplate