! gridops.f90 ! written by Tim Mitchell, Tyndall Centre for Climate Change Research ! 27th July 2001, 14th September 2001 ! module in which routines to handle transformations between grids are held ! also routine to obtain distance between two points on globe ! contains: ! NatGridToLatLong: converts from National Grid numbers to lat/long ! LatLongToNatGrid: converts to National Grid numbers from lat/long ! GetDistance : gives distance between two points on globe module GridOps implicit none contains !******************************************************************************* ! supply with Got (NLat,NLon)=.TRUE. where a stn is held ! returns Need=.TRUE. for all boxes within range of >0 stns ! Got and Need must already be allocated ; Range is given in km subroutine IdentifyRelevant (Got,Need,Range) logical, pointer, dimension (:,:) :: Got,Need integer, allocatable, dimension (:) :: ExeAdd real, intent(in) :: Range real, parameter :: MissVal = -999.0 real :: BoxLatDeg,BoxLonDeg, Extra,Dist, Lat,Lon integer :: NLat,NLon,NExe,NWye, AllocStat integer :: XLat,XLon,XExe,XWye, XSeekLat,XSeekLon NLat=size(Got,1) ; NLon=size(Got,2) BoxLatDeg=180.0/real(NLat) ; BoxLonDeg=360.0/real(NLon) Need=.FALSE. allocate (ExeAdd(NLat),stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### Screen: dealloc #####" ExeAdd=MissVal Extra=0 ; NWye=MissVal ! find no. of N-S boxes to add do Extra=Extra+1 Dist=GetDistance(0.0,0.0,(Extra*BoxLatDeg),0.0) if (Dist.GT.Range) NWye=Extra-1 if (NWye.EQ.0) NWye=1 if (NWye.NE.MissVal.OR.(Extra*2).GE.NLat) exit end do if (NWye.EQ.MissVal) NWye=Extra do XLat=1,NLat ! find no. of E-W boxes to add, by lat Extra=0 ; Lat=-180.0+((real(NLat)-0.5)*BoxLatDeg) do Extra=Extra+1 Dist=GetDistance(Lat,0.0,Lat,(Extra*BoxLonDeg)) if (Dist.GT.Range) ExeAdd(XLat)=Extra-1 if (ExeAdd(XLat).EQ.0) ExeAdd(XLat)=1 if (ExeAdd(XLat).NE.MissVal.OR.(Extra*2).GE.NLon) exit end do if (ExeAdd(XLat).EQ.MissVal) ExeAdd(XLat)=Extra ! write (99,"(2i4,f9.2)"), XLat,ExeAdd(XLat),Lat ! @@@@@@@@@@@@@@@@ end do do XLat=1,NLat NExe=ExeAdd(XLat) do XLon=1,NLon if (Got(XLat,XLon)) then do XExe=(0-NExe),NExe XSeekLon=XLon+XExe if (XSeekLon.LE.0) XSeekLon=XSeekLon+NLon if (XSeekLon.GT.NLon) XSeekLon=XSeekLon-NLon do XWye=(0-NWye),NWye XSeekLat=XLat+XWye if (XSeekLat.LE.0) XSeekLat=1-XSeekLat if (XSeekLat.GT.NLat) XSeekLat=1-XSeekLat+(2*NLat) Need(XSeekLat,XSeekLon)=.TRUE. end do end do end if end do end do end subroutine IdentifyRelevant !******************************************************************************* function GetDistance (Lat1,Lon1,Lat2,Lon2) real :: GetDistance real :: Lat1,Lon1,Lat2,Lon2 real :: KayB,KayR real :: VarThet1,VarThet2,VarPhi1,VarPhi2,VarX1,VarX2,VarY1,VarY2,VarZ1,VarZ2 real :: VarH,VarTemp,VarAngle KayB=180.0/(22.0/7.0) ; KayR=6367.65 VarThet2=Lat2/KayB ; VarThet1=Lat1/KayB VarPhi2=Lon2/KayB ; VarPhi1=Lon1/KayB VarX1=sin(VarPhi1)*cos(VarThet1) ; VarX2=sin(VarPhi2)*cos(VarThet2) VarY1=cos(VarPhi1)*cos(VarThet1) ; VarY2=cos(VarPhi2)*cos(VarThet2) VarZ1=sin(VarThet1) ; VarZ2=sin(VarThet2) VarH = (VarX1-VarX2)**2+(VarY1-VarY2)**2+(VarZ1-VarZ2)**2 VarTemp=(1+1-Varh)/2 if(VarTemp.lt.-1.0000000) VarTemp=-1.0000 if(VarTemp.gt. 1.0000000) VarTemp= 1.0000 VarAngle=acos(VarTemp) GetDistance=VarAngle*KayR end function GetDistance !******************************************************************************* ! converts from National Grid numbers to lat/long ! East,North should be given in metres ! Lat,Long are returned in degrees as a decimal, rather than degrees/minutes/seconds subroutine NatGridToLatLong (East,North,LatDeg,LonDeg) integer, parameter :: big = selected_real_kind (11,99) real (kind=big), intent (in) :: East,North real (kind=big), intent (out) :: LatDeg,LonDeg real (kind=big), parameter :: Ka = 6377563.396 real (kind=big), parameter :: Kb = 6356256.910 real (kind=big), parameter :: Kpi = 3.14159265358979 real (kind=big), parameter :: KN0 = -100000.0 real (kind=big), parameter :: KE0 = 400000.0 real (kind=big), parameter :: KF0 = 0.9996012717 real (kind=big), parameter :: KThi0 = 49.0 real (kind=big), parameter :: KLam0 = -2.0 real (kind=big) :: Lat,Lon real (kind=big) :: Ve2, VThi0, VLam0, VThiDash, VM, Vro, Vv, Vnu2, Vsin, Vtan, Vsec real (kind=big) :: V7, V8, V9, V10, V11, V12, V12A, Veas integer :: QClose = 0 !*************************************** Ve2 = ((Ka*Ka)-(Kb*Kb))/(Ka*Ka) VThi0 = (KThi0 / real(180,big)) * Kpi VLam0 = (KLam0 / real(180,big)) * Kpi VThiDash = ((North-KN0)/(Ka*KF0)) + VThi0 do VM = CalcM (Ka,Kb,KF0,VThi0,VThiDash) if ((North-KN0-VM).LT.real(0.001,big)) QClose = 1 VThiDash = ((North-KN0-VM)/(Ka*KF0)) + VThiDash if (QClose.EQ.1) exit end do Vsin = sin(VThiDash) ; Vtan = tan(VThiDash) ; Vsec = real(1,big) / cos(VThiDash) Vv = Ka * KF0 * ((real(1,big)-(Ve2*Vsin*Vsin))**(real(-0.5,big))) Vro = Ka * KF0 * (real(1,big)-Ve2) * ((real(1,big)-(Ve2*Vsin*Vsin))**(real(-1.5,big))) Vnu2 = (Vv/Vro) - real(1,big) V7 = Vtan / (real(2,big)*Vro*Vv) V8 = (Vtan / (real(24,big)*Vro*Vv*Vv*Vv)) * (real(5,big)+(real(3,big)*Vtan*Vtan)+Vnu2-(real(9,big)*Vtan*Vtan*Vnu2)) V9 = (Vtan / (real(720,big)*Vro*Vv*Vv*Vv*Vv*Vv)) * (real(61,big)+(real(90,big)*Vtan*Vtan)+(real(45,big)*Vtan*Vtan*Vtan*Vtan)) V10 = Vsec / Vv V11 = (Vsec / (real(6,big)*Vv*Vv*Vv)) * ((Vv/Vro)+(real(2,big)*Vtan*Vtan)) V12 = (Vsec / (real(120,big)*Vv*Vv*Vv*Vv*Vv)) * (real(5,big)+(real(28,big)*Vtan*Vtan)+(real(24,big)*Vtan*Vtan*Vtan*Vtan)) V12A = (Vsec / (real(5040,big)*(Vv**7))) * (real(61,big)+(real(662,big)*(Vtan**2))+(real(1320,big)*(Vtan**4))+(real(720,big)*(Vtan**6))) Veas = East - KE0 LatDeg = VThiDash - (V7*(Veas**2)) + (V8*(Veas**4)) - (V9*(Veas**6)) LonDeg = VLam0 + (V10*Veas) - (V11*(Veas**3)) + (V12*(Veas**5)) - (V12A*(Veas**7)) LatDeg = (LatDeg/Kpi) * real(180,big) LonDeg = (LonDeg/Kpi) * real(180,big) end subroutine NatGridToLatLong !******************************************************************************* ! converts from lat/long to National Grid numbers ! East,North are given in metres ! Lat,Long are required, as: ! EITHER in degrees as a decimal, with minutes and seconds both set to zero ! OR in degrees, minutes, seconds subroutine LatLongToNatGrid (East,North,LatDeg,LatMin,LatSec,LonDeg,LonMin,LonSec) integer, parameter :: big = selected_real_kind (11,99) real (kind=big), intent (out) :: East,North real (kind=big), intent (in) :: LatDeg,LatMin,LatSec,LonDeg,LonMin,LonSec real (kind=big), parameter :: Ka = 6377563.396 real (kind=big), parameter :: Kb = 6356256.910 real (kind=big), parameter :: Kpi = 3.14159265358979 real (kind=big), parameter :: KN0 = -100000.0 real (kind=big), parameter :: KE0 = 400000.0 real (kind=big), parameter :: KF0 = 0.9996012717 real (kind=big), parameter :: KThi0 = 49.0 real (kind=big), parameter :: KLam0 = -2.0 real (kind=big) :: Lat, Lon real (kind=big) :: Ve2, Vn,Vv, Vro, Vnu2, VM, V1, V2, V3, V3A, V4, V5, V6, V6A, V6B, V6C, Br, Br2,VThi0,VLam0 !*************************************** Lat = LatDeg + (LatMin/real(60,big)) + (LatSec/real(3600,big)) Lon = LonDeg + (LonMin/real(60,big)) + (LonSec/real(3600,big)) Lat = (Lat * Kpi) / real(180,big) Lon = (Lon * Kpi) / real(180,big) VThi0 = (KThi0 / real(180,big)) * Kpi VLam0 = (KLam0 / real(180,big)) * Kpi Ve2 = ((Ka*Ka)-(Kb*Kb))/(Ka*Ka) Vn = (Ka-Kb) / (Ka+Kb) Vv = Ka * KF0 * ((real(1,big) - (Ve2 * sin(Lat) * sin(Lat))) ** (0-0.5)) Vro = Ka * KF0 * (real(1,big) - Ve2) * ((real(1,big) - (Ve2 * sin(Lat) * sin(Lat))) ** (0-1.5)) Vnu2 = (Vv/Vro) - real(1,big) VM = CalcM (Ka,Kb,KF0,VThi0,Lat) V1 = VM + KN0 V2 = (Vv/real(2,big))*sin(Lat)*cos(Lat) V3 = (Vv/real(24,big))*sin(Lat)*(cos(Lat)**3)*(real(5,big)-(tan(Lat)*tan(Lat))+(real(9,big)*Vnu2)) V3A = (Vv/real(720,big))*sin(Lat)*(cos(Lat)**5)*(real(61,big)-(real(58,big)*(tan(Lat)**2))+(tan(Lat)**4)) V4 = (Vv*cos(Lat)) V5 = (Vv/real(6,big))*(cos(Lat)**3)*((Vv/Vro)-(tan(Lat)**2)) V6A = (Vv/real(120,big))*(cos(Lat)**5) V6B = real(5,big)-(real(18,big)*(tan(Lat)**2))+(tan(Lat)**4)+(real(14,big)*Vnu2) V6C = real(58,big)*(tan(Lat)**2)*Vnu2 V6 = V6A * (V6B - V6C) Br = Lon - VLam0 Br2 = Br * Br North = V1 + (V2*Br2) + (V3*Br2*Br2) + (V3A*Br2*Br2*Br2) East = KE0 + (V4*Br) + (V5*Br*Br2) + (V6*Br*Br2*Br2) end subroutine LatLongToNatGrid !******************************************************************************* ! calcs 'M' function CalcM (Va,Vb,VF0,VThi0,VThi) integer, parameter :: big = selected_real_kind (11,99) real (kind=big) :: CalcM,Vn real (kind=big), intent (in) :: Va,Vb,VF0,VThi0,VThi Vn = (Va-Vb) / (Va+Vb) CalcM = (real(1,big)+Vn+(real(1.25,big)*Vn*Vn)+(real(1.25,big)*Vn*Vn*Vn)) * (VThi-VThi0) CalcM = CalcM - (((real(3,big)*Vn)+(real(3,big)*Vn*Vn)+(real((2.625),big)*Vn*Vn*Vn)) * sin(VThi-VThi0) * cos(VThi+VThi0)) CalcM = CalcM + (((real(1.875,big)*Vn*Vn)+(real(1.875,big)*Vn*Vn*Vn)) * sin(real(2,big)*(VThi-VThi0)) * cos(real(2,big)*(VThi+VThi0))) CalcM = CalcM - (((real(35,big)/real(24,big))*Vn*Vn*Vn) * sin(real(3,big)*(VThi-VThi0)) * cos(real(3,big)*(VThi+VThi0))) CalcM = CalcM * Vb * VF0 end function CalcM !******************************************************************************* end module GridOps