1module slantProfileLatLon_mod
2 ! MODULE slantProfileLatLon_mod (prefix='slp' category='5. Observation operators')
3 !
4 !:Purpose: To calculate latitudes/longitudes on slant-path based on
5 ! ColumnData.
6 !
7 use midasMpi_mod
8 use earthConstants_mod
9 use mathPhysConstants_mod
10 use utilities_mod
11 use obsSpaceData_mod
12 use horizontalCoord_mod
13 use tovsNL_mod
14 use codtyp_mod
15 use getGridPosition_mod
16 use radialVelocity_mod
17
18 implicit none
19 save
20 private
21
22 ! public procedures
23 public :: slp_calcLatLonTovs, slp_calcLatLonRO, slp_calcLatLonRadar
24
25 ! private module variables and derived types
26 logical :: nmlAlreadyRead = .false.
27
28 ! namelist variables
29 real(4) :: toleranceHeightDiff ! threshold of height diff (in m) for convergence of slant path
30 integer :: maxNumIteration ! maximum number of iterations for determining slant path
31
32
33contains
34
35 subroutine slp_calcLatLonTovs(obsSpaceData, hco, headerIndex, height3D_T_r4, height3D_M_r4, &
36 latSlantLev_T, lonSlantLev_T, latSlantLev_M, lonSlantLev_M, latSlantLev_S, lonSlantLev_S)
37 !
38 !:Purpose: call the computation of lat/lon on the slant path for radiance
39 ! observations, iteratively. To replace the vertical columns with
40 ! line-of-sight slanted columns.
41 !
42 implicit none
43
44 ! Arguments:
45 type(struct_obs), intent(in) :: obsSpaceData
46 type(struct_hco), intent(in) :: hco
47 integer, intent(in) :: headerIndex
48 real(4), intent(in) :: height3D_T_r4(:,:,:)
49 real(4), intent(in) :: height3D_M_r4(:,:,:)
50 real(8), intent(out) :: latSlantLev_T(:)
51 real(8), intent(out) :: lonSlantLev_T(:)
52 real(8), intent(out) :: latSlantLev_M(:)
53 real(8), intent(out) :: lonSlantLev_M(:)
54 real(8), intent(out) :: latSlantLev_S
55 real(8), intent(out) :: lonSlantLev_S
56
57 ! Locals:
58 real(8) :: lat, lon, azimuthAngle
59 integer :: idatyp
60 integer :: ierr, fnom, fclos, nulnam
61 integer :: nlev_T, lev_T, nlev_M, lev_M
62
63 namelist /namSlantPath/ toleranceHeightDiff, maxNumIteration
64
65 if ( .not. nmlAlreadyRead ) then
66 nmlAlreadyRead = .true.
67
68 ! default values
69 toleranceHeightDiff = 10.0
70 maxNumIteration = 1
71
72 ! reading namelist variables
73 nulnam = 0
74 ierr = fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
75 read(nulnam, nml = namSlantPath, iostat = ierr)
76 if (ierr /= 0) write(*,*) 'slp_calcLatLonTovs: namSlantPath is missing in the namelist. The default value will be taken.'
77 if (mmpi_myid == 0) write(*, nml = namSlantPath)
78 ierr = fclos(nulnam)
79 end if
80
81 nlev_M = size(height3D_M_r4,3)
82 nlev_T = size(height3D_T_r4,3)
83
84 lat = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
85 lon = obs_headElem_r(obsSpaceData,OBS_LON,headerIndex)
86 if (lon < 0.0d0 ) lon = lon + 2.0d0*MPC_PI_R8
87 if (lon >= 2.0d0*MPC_PI_R8) lon = lon - 2.0d0*MPC_PI_R8
88 azimuthAngle = tvs_getCorrectedSatelliteAzimuth(obsSpaceData, headerIndex)
89
90 !SSMIS special case
91 idatyp = obs_headElem_i(obsSpaceData,OBS_ITY,headerIndex)
92 if ( idatyp == codtyp_get_codtyp('ssmis') ) then
93 azimuthAngle = obs_missingValue_R
94 end if
95
96 ! loop through all thermo levels
97 do lev_T = 1, nlev_T
98 call solveIterativelyForLatLon(height3D_T_r4(:,:,lev_T), latSlantLev_T(lev_T), lonSlantLev_T(lev_T))
99 end do
100
101 ! loop through all momentum levels
102 do lev_M = 1, nlev_M
103 call solveIterativelyForLatLon(height3D_M_r4(:,:,lev_M), latSlantLev_M(lev_M), lonSlantLev_M(lev_M))
104 end do
105
106 ! then surface level (geoid in the case of radiances)
107 latSlantLev_S = latSlantLev_T(nlev_T)
108 lonSlantLev_S = lonSlantLev_T(nlev_T)
109
110 contains
111
112 subroutine solveIterativelyForLatLon(heightField2D, latSlant, lonSlant)
113 implicit none
114
115 ! Arguments:
116 real(4), intent(in) :: heightField2D(:,:)
117 real(8), intent(out) :: latSlant
118 real(8), intent(out) :: lonSlant
119
120 ! Locals:
121 logical :: doIteration
122 integer :: numIteration
123 real(4) :: heightInterp_r4, heightIntersect_r4, heightDiff_r4
124
125 ! find the interpolated height
126 call heightBilinearInterp(lat, lon, hco, heightField2D, heightInterp_r4)
127
128 doIteration = .true.
129 numIteration = 0
130 while_doIteration: do while (doIteration)
131
132 numIteration = numIteration + 1
133
134 call findIntersectLatlon(obsSpaceData, headerIndex, heightInterp_r4, azimuthAngle, latSlant, lonSlant)
135
136 ! find the interpolated height
137 call heightBilinearInterp(latSlant, lonSlant, hco, heightField2D, heightIntersect_r4)
138
139 heightDiff_r4 = abs(heightInterp_r4-heightIntersect_r4)
140 if ( heightDiff_r4 < toleranceHeightDiff .or. &
141 numIteration >= maxNumIteration ) doIteration = .false.
142
143 end do while_doIteration
144
145 end subroutine solveIterativelyForLatLon
146
147
148 end subroutine slp_calcLatLonTovs
149
150
151 subroutine findIntersectLatlon(obsSpaceData, headerIndex, height_r4, azimuthAngle, latSlant, lonSlant)
152 !
153 !:Purpose: Computation of lat/lon of the intersection between model level
154 ! and the slant line-of-sight for radiance observations.
155 !
156 implicit none
157
158 ! Arguments:
159 type(struct_obs), intent(in) :: obsSpaceData
160 real(4), intent(in) :: height_r4
161 integer, intent(in) :: headerIndex
162 real(8), intent(in) :: azimuthAngle
163 real(8), intent(out) :: latSlant
164 real(8), intent(out) :: lonSlant
165
166 ! Locals:
167 real(8) :: lat, lon, geometricHeight
168 real(8) :: zenithAngle, zenithAngle_rad, azimuthAngle_rad, elevationAngle_rad, distAlongPath
169 real(8) :: obsCordGlb(3), slantPathCordGlb(3), unitx(3), unity(3), unitz(3), unitSatLoc(3), unitSatGlb(3)
170
171 ! read lat/lon/angles from obsSpaceData
172 lat = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
173 lon = obs_headElem_r(obsSpaceData,OBS_LON,headerIndex)
174 if (lon < 0.0d0 ) lon = lon + 2.0d0*MPC_PI_R8
175 if (lon >= 2.0d0*MPC_PI_R8) lon = lon - 2.0d0*MPC_PI_R8
176
177 if ( azimuthAngle /= obs_missingValue_R) then
178
179 zenithAngle = obs_headElem_r(obsSpaceData,OBS_SZA,headerIndex)
180
181 ! convert angles to radian unit
182 azimuthAngle_rad = azimuthAngle * MPC_RADIANS_PER_DEGREE_R8
183 zenithAngle_rad = zenithAngle * MPC_RADIANS_PER_DEGREE_R8
184 elevationAngle_rad = 0.5d0 * MPC_PI_R8 - zenithAngle_rad
185
186 obsCordGlb = ec_ra * (/ cos(lat)*cos(lon), cos(lat)*sin(lon), sin(lat) /)
187 unitz = (/ cos(lat)*cos(lon), cos(lat)*sin(lon) , sin(lat) /)
188 unitx = (/ -sin(lon) , cos(lon) , 0.d0 /)
189 unity = (/ -sin(lat)*cos(lon), -sin(lat)*sin(lon), cos(lat) /)
190
191 ! unit vector towards satellite in local coordinate
192 unitSatLoc = (/ cos(elevationAngle_rad)*sin(azimuthAngle_rad) , &
193 cos(elevationAngle_rad)*cos(azimuthAngle_rad) , &
194 sin(elevationAngle_rad) /)
195 ! unit vector towards satellite in global coordinate
196 unitSatGlb = unitSatLoc(1) * unitx + unitSatLoc(2) * unity + unitSatLoc(3) * unitz
197
198 ! Geometric altitude
199 geometricHeight = real(height_r4,8)
200
201 ! distance along line of sight
202 distAlongPath = geometricHeight / cos(zenithAngle_rad)
203
204 slantPathCordGlb(:) = obsCordGlb(:) + distAlongPath * unitSatGlb(:)
205
206 latSlant = atan(slantPathCordGlb(3)/sqrt(slantPathCordGlb(1)**2+slantPathCordGlb(2)**2))
207 lonSlant = atan2(slantPathCordGlb(2),slantPathCordGlb(1))
208 if (lonSlant < 0.0d0 ) lonSlant = lonSlant + 2.0d0*MPC_PI_R8
209 if (lonSlant >= 2.0d0*MPC_PI_R8) lonSlant = lonSlant - 2.0d0*MPC_PI_R8
210
211 else
212
213 latSlant = lat
214 lonSlant = lon
215
216 end if
217
218 end subroutine findIntersectLatlon
219
220
221 subroutine heightBilinearInterp(lat, lon, hco, height_r4, heightInterp_r4)
222 !
223 !:Purpose: To interpolate the 2D height field to the obs location
224 !
225 implicit none
226
227 ! Arguments:
228 real(8), intent(in) :: lat
229 real(8), intent(in) :: lon
230 type(struct_hco), intent(in) :: hco
231 real(4), intent(in) :: height_r4(:,:)
232 real(4), intent(out) :: heightInterp_r4
233
234 ! Locals:
235 integer :: ierr, niP1
236 integer :: latIndex, lonIndex, latIndex2, lonIndex2, lonIndexP1
237 integer :: subGridIndex, subGridForInterp, numSubGridsForInterp
238 integer :: ipoint, gridptCount
239 integer :: latIndexVec(8), lonIndexVec(8)
240 real(8) :: WeightVec(8)
241 real(8) :: dldx, dldy
242 real(8) :: weightsSum
243 real(4) :: lon_deg_r4, lat_deg_r4
244 real(4) :: xpos_r4, ypos_r4, xpos2_r4, ypos2_r4
245 logical :: latlonOutsideGrid
246
247 lat_deg_r4 = real(lat * MPC_DEGREES_PER_RADIAN_R8)
248 lon_deg_r4 = real(lon * MPC_DEGREES_PER_RADIAN_R8)
249 ierr = gpos_getPositionXY( hco%EZscintID, &
250 xpos_r4, ypos_r4, xpos2_r4, ypos2_r4, &
251 lat_deg_r4, lon_deg_r4, subGridIndex )
252
253 ! Allow for periodicity in Longitude for global Gaussian grid
254 if ( hco%grtyp == 'G' .or. (hco%grtyp == 'Z' .and. hco%global) ) then
255 niP1 = hco%ni + 1
256 else
257 niP1 = hco%ni
258 end if
259
260 ! Check if the interpolated lat/lon is outside the hco domain
261 latlonOutsideGrid = ( xpos_r4 < 1.0 .or. &
262 xpos_r4 > real(niP1) .or. &
263 ypos_r4 < 1.0 .or. &
264 ypos_r4 > real(hco%nj) )
265
266 if ( latlonOutsideGrid ) then
267 write(*,*) 'heightBilinearInterp: interpolated lat/lon outside the hco domain.'
268 write(*,*) ' position lon, lat = ', lon_deg_r4, lat_deg_r4
269 write(*,*) ' position x, y = ', xpos_r4, ypos_r4
270
271 ! if above or below domain
272 if( ypos_r4 < 1.0 ) ypos_r4 = 1.0
273 if( ypos_r4 > real(hco%nj) ) ypos_r4 = real(hco%nj)
274
275 ! if on the left or right longitude band, move it to the edge of this longitude band
276 if( xpos_r4 < 1.0 ) xpos_r4 = 1.0
277 if( xpos_r4 > real(niP1) ) xpos_r4 = real(niP1)
278 write(*,*) ' new position x, y = ', xpos_r4, ypos_r4
279
280 end if
281
282 ! Find the lower-left grid point next to the observation
283 if ( xpos_r4 == real(niP1) ) then
284 lonIndex = floor(xpos_r4) - 1
285 else
286 lonIndex = floor(xpos_r4)
287 end if
288 if ( xpos2_r4 == real(niP1) ) then
289 lonIndex2 = floor(xpos2_r4) - 1
290 else
291 lonIndex2 = floor(xpos2_r4)
292 end if
293
294 if ( ypos_r4 == real(hco%nj) ) then
295 latIndex = floor(ypos_r4) - 1
296 else
297 latIndex = floor(ypos_r4)
298 end if
299 if ( ypos2_r4 == real(hco%nj) ) then
300 latIndex2 = floor(ypos2_r4) - 1
301 else
302 latIndex2 = floor(ypos2_r4)
303 end if
304
305 if ( hco%grtyp == 'U' ) then
306 if ( ypos_r4 == real(hco%nj/2) ) then
307 latIndex = floor(ypos_r4) - 1
308 else
309 latIndex = floor(ypos_r4)
310 end if
311 if ( ypos2_r4 == real(hco%nj/2) ) then
312 latIndex2 = floor(ypos2_r4) - 1
313 else
314 latIndex2 = floor(ypos2_r4)
315 end if
316 end if
317
318 ! Handle periodicity in longitude
319 lonIndexP1 = lonIndex + 1
320 if ( lonIndexP1 == hco%ni + 1 ) lonIndexP1 = 1
321
322 ! Check if location is in between Yin and Yang (should not happen)
323 if ( hco%grtyp == 'U' ) then
324 if ( ypos_r4 > real(hco%nj/2) .and. &
325 ypos_r4 < real((hco%nj/2)+1) ) then
326 write(*,*) 'heightBilinearInterp: WARNING, obs position in between Yin and Yang!!!'
327 write(*,*) ' xpos, ypos = ', xpos_r4, ypos_r4
328 end if
329 if ( ypos2_r4 > real(hco%nj/2) .and. &
330 ypos2_r4 < real((hco%nj/2)+1) ) then
331 write(*,*) 'heightBilinearInterp: WARNING, obs position in between Yin and Yang!!!'
332 write(*,*) ' xpos2, ypos2 = ', xpos2_r4, ypos2_r4
333 end if
334 end if
335
336 if ( subGridIndex == 3 ) then
337 ! both subGrids involved in interpolation, so first treat subGrid 1
338 numSubGridsForInterp = 2
339 subGridIndex = 1
340 else
341 ! only 1 subGrid involved in interpolation
342 numSubGridsForInterp = 1
343 end if
344
345 gridptCount = 0
346
347 do subGridForInterp = 1, numSubGridsForInterp
348
349 ! Compute the 4 weights of the bilinear interpolation
350 if ( subGridForInterp == 1 ) then
351 ! when only 1 subGrid involved, subGridIndex can be 1 or 2
352 dldx = real(xpos_r4,8) - real(lonIndex,8)
353 dldy = real(ypos_r4,8) - real(latIndex,8)
354 else
355 ! when 2 subGrids, subGridIndex is set to 1 for 1st iteration, 2 for second
356 subGridIndex = 2
357 lonIndex = lonIndex2
358 latIndex = latIndex2
359 lonIndexP1 = lonIndex2 + 1
360 dldx = real(xpos2_r4,8) - real(lonIndex,8)
361 dldy = real(ypos2_r4,8) - real(latIndex,8)
362 end if
363
364 gridptCount = gridptCount + 1
365 latIndexVec(gridptCount) = latIndex
366 lonIndexVec(gridptCount) = lonIndex
367 WeightVec(gridptCount) = (1.d0-dldx) * (1.d0-dldy)
368
369 gridptCount = gridptCount + 1
370 latIndexVec(gridptCount) = latIndex
371 lonIndexVec(gridptCount) = lonIndexP1
372 WeightVec(gridptCount) = dldx * (1.d0-dldy)
373
374 gridptCount = gridptCount + 1
375 latIndexVec(gridptCount) = latIndex + 1
376 lonIndexVec(gridptCount) = lonIndex
377 WeightVec(gridptCount) = (1.d0-dldx) * dldy
378
379 gridptCount = gridptCount + 1
380 latIndexVec(gridptCount) = latIndex + 1
381 lonIndexVec(gridptCount) = lonIndexP1
382 WeightVec(gridptCount) = dldx * dldy
383
384 end do ! subGrid
385
386 weightsSum = sum(WeightVec(1:gridptCount))
387 if ( weightsSum > 0.d0 ) then
388
389 WeightVec(1:gridptCount) = WeightVec(1:gridptCount) / weightsSum
390
391 else
392
393 call utl_abort('heightBilinearInterp: weightsSum smaller than 0.')
394
395 end if
396
397 ! perform the bi-linear interpolation
398 heightInterp_r4 = 0.0
399 do ipoint = 1, gridptCount
400
401 if ( latIndexVec(ipoint) > hco%nj ) then
402 write(*,*) 'heightBilinearInterp: latIndexVec(ipoint) > hco%nj: latIndex=',latIndexVec(ipoint),', lonIndex=',lonIndexVec(ipoint)
403 write(*,*) 'lat_deg_r4=',lat_deg_r4,', lon_deg_r4=',lon_deg_r4
404 write(*,*) 'ypos_r4=',ypos_r4,', xpos_r4=',xpos_r4
405 end if
406 if ( latIndexVec(ipoint) < 1 ) then
407 write(*,*) 'heightBilinearInterp: latIndexVec(ipoint) < 1: latIndex=',latIndexVec(ipoint),', lonIndex=',lonIndexVec(ipoint)
408 write(*,*) 'lat_deg_r4=',lat_deg_r4,', lon_deg_r4=',lon_deg_r4
409 write(*,*) 'ypos_r4=',ypos_r4,', xpos_r4=',xpos_r4
410 end if
411
412 if ( lonIndexVec(ipoint) > hco%ni ) then
413 write(*,*) 'heightBilinearInterp: lonIndexVec(ipoint) > hco%ni: latIndex=',latIndexVec(ipoint),', lonIndex=',lonIndexVec(ipoint)
414 write(*,*) 'lat_deg_r4=',lat_deg_r4,', lon_deg_r4=',lon_deg_r4
415 write(*,*) 'ypos_r4=',ypos_r4,', xpos_r4=',xpos_r4
416 end if
417 if ( lonIndexVec(ipoint) < 1 ) then
418 write(*,*) 'heightBilinearInterp: lonIndexVec(ipoint) < 1: latIndex=',latIndexVec(ipoint),', lonIndex=',lonIndexVec(ipoint)
419 write(*,*) 'lat_deg_r4=',lat_deg_r4,', lon_deg_r4=',lon_deg_r4
420 write(*,*) 'ypos_r4=',ypos_r4,', xpos_r4=',xpos_r4
421 end if
422
423 latlonOutsideGrid = ( latIndexVec(ipoint) < 1 .or. &
424 latIndexVec(ipoint) > hco%nj .or. &
425 lonIndexVec(ipoint) < 1 .or. &
426 lonIndexVec(ipoint) > hco%ni )
427
428 if ( latlonOutsideGrid ) &
429 call utl_abort('heightBilinearInterp: lat/lon outside the domain.')
430
431 heightInterp_r4 = heightInterp_r4 + &
432 real(WeightVec(ipoint),4) * &
433 height_r4(lonIndexVec(ipoint), latIndexVec(ipoint))
434 end do
435
436 end subroutine heightBilinearInterp
437
438 subroutine slp_calcLatLonRO(obsSpaceData, hco, headerIndex, &
439 height3D_T_r4, height3D_M_r4, &
440 latSlantLev_T, lonSlantLev_T, latSlantLev_M, lonSlantLev_M, latSlantLev_S, lonSlantLev_S )
441 !
442 !:Purpose: call the computation of lat/lon on the slant path for GPSRO
443 ! observations, iteratively. To replace the vertical columns with
444 ! slanted columns.
445 !
446 implicit none
447
448 ! Arguments:
449 type(struct_obs), intent(inout) :: obsSpaceData
450 type(struct_hco), intent(in) :: hco
451 integer, intent(in) :: headerIndex
452 real(4), intent(in) :: height3D_T_r4(:,:,:)
453 real(4), intent(in) :: height3D_M_r4(:,:,:)
454 real(8), intent(out) :: latSlantLev_T(:)
455 real(8), intent(out) :: lonSlantLev_T(:)
456 real(8), intent(out) :: latSlantLev_M(:)
457 real(8), intent(out) :: lonSlantLev_M(:)
458 real(8), intent(out) :: latSlantLev_S
459 real(8), intent(out) :: lonSlantLev_S
460
461 ! Locals:
462 real(8) :: latr, lonr, height, rad, dH, hmin
463 integer :: bodyIndex, imin
464 real(8), allocatable :: Lat_Obs(:), Lon_Obs(:), Hgt_Obs(:)
465 real(4), allocatable :: H_M(:,:), H_T(:,:)
466 integer :: nObs, iObs
467 integer :: levIndex, nlev_M, nlev_T
468
469 nlev_M = size(height3D_M_r4,3)
470 nlev_T = size(height3D_T_r4,3)
471
472 ! Header, and Obs counting:
473 call obs_set_current_header_list(obsSpaceData,'RO')
474 call obs_set_current_body_list(obsSpaceData, headerIndex)
475 Rad = obs_headElem_r(obsSpaceData,OBS_TRAD,headerIndex)
476 nObs = 0
477 BODY: do
478 bodyIndex = obs_getBodyIndex(obsSpaceData)
479 if (bodyIndex < 0) exit BODY
480 nObs = nObs + 1
481 end do BODY
482
483 call obs_set_current_body_list(obsSpaceData, headerIndex)
484
485 allocate(Hgt_Obs(nObs), Lat_Obs(nObs), Lon_Obs(nObs))
486 allocate(H_M(nObs,nlev_M), H_T(nObs,nlev_T))
487
488 do iObs = 1,nObs
489 bodyIndex = obs_getBodyIndex(obsSpaceData)
490 if (bodyIndex < 0) exit
491
492 height = obs_bodyElem_r(obsSpaceData,OBS_PPP,bodyIndex)
493 ! If the vertical coordinate is an impact parameter (6e6<himp<7e6), subtract radius:
494 if (6.e6 < height .and. height < 7.e6) height = height-rad
495 Hgt_Obs(iObs) = height
496 latr = obs_bodyElem_r(obsSpaceData,OBS_LATD,bodyIndex)
497 lonr = obs_bodyElem_r(obsSpaceData,OBS_LOND,bodyIndex)
498 if (lonr < 0.d0 ) lonr = lonr + 2.d0*MPC_PI_R8
499 if (lonr >= 2.d0*MPC_PI_R8) lonr = lonr - 2.d0*MPC_PI_R8
500 Lat_Obs(iObs) = latr
501 Lon_Obs(iObs) = lonr
502 do levIndex = 1, nlev_M
503 call heightBilinearInterp(latr, lonr, hco, height3D_M_r4(:,:,levIndex), &
504 H_M(iObs,levIndex))
505 end do
506 do levIndex = 1, nlev_T
507 call heightBilinearInterp(latr, lonr, hco, height3D_T_r4(:,:,levIndex), &
508 H_T(iObs,levIndex))
509 end do
510 end do
511
512 do levIndex = 1, nlev_M
513 hmin = 1.e30
514 imin = -1
515 do iObs = 1, nObs
516 dH = abs(H_M(iObs,levIndex)-Hgt_Obs(iObs))
517 if (dH < hmin) then
518 hmin = dH
519 imin = iObs
520 end if
521 end do
522 latSlantLev_M(levIndex) = Lat_Obs(imin)
523 lonSlantLev_M(levIndex) = Lon_Obs(imin)
524 end do
525
526 do levIndex = 1, nlev_T
527 hmin = 1.e30
528 imin = -1
529 do iObs = 1, nObs
530 dH = abs(H_T(iObs,levIndex)-Hgt_Obs(iObs))
531 if (dH < hmin) then
532 hmin = dH
533 imin = iObs
534 end if
535 end do
536 latSlantLev_T(levIndex) = Lat_Obs(imin)
537 lonSlantLev_T(levIndex) = Lon_Obs(imin)
538 end do
539 latSlantLev_S = latSlantLev_T(nlev_T)
540 lonSlantLev_S = lonSlantLev_T(nlev_T)
541
542 deallocate(H_T, H_M)
543 deallocate(Lon_Obs, Lat_Obs, Hgt_Obs)
544
545 end subroutine slp_calcLatLonRO
546
547 subroutine slp_calcLatLonRadar(obsSpaceData, hco, headerIndex, height3D_T_r4, height3D_M_r4, &
548 latSlantLev_T, lonSlantLev_T, latSlantLev_M, lonSlantLev_M, latSlantLev_S, lonSlantLev_S)
549 !
550 !:Purpose: call the computation of lat/lon on the slant path for radar
551 ! observations, iteratively. To replace the vertical columns with
552 ! radar beam columns .
553 !
554 implicit none
555
556 ! Arguments:
557 type(struct_obs), intent(in) :: obsSpaceData
558 type(struct_hco), intent(in) :: hco
559 integer, intent(in) :: headerIndex
560 real(4), intent(in) :: height3D_T_r4(:,:,:)
561 real(4), intent(in) :: height3D_M_r4(:,:,:)
562 real(8), intent(out) :: latSlantLev_T(:)
563 real(8), intent(out) :: lonSlantLev_T(:)
564 real(8), intent(out) :: latSlantLev_M(:)
565 real(8), intent(out) :: lonSlantLev_M(:)
566 real(8), intent(out) :: latSlantLev_S
567 real(8), intent(out) :: lonSlantLev_S
568
569 ! Locals:
570 real(8) :: antennaLat, antennaLon, latSlant, lonSlant, beamElevation, beamAzimuth
571 real(8) :: radarAltitude, beamRangeStart, beamRangeEnd
572 integer :: nlev_M,lev_M, nlev_T,lev_T
573
574 nlev_M = size(height3D_M_r4,3)
575 nlev_T = size(height3D_T_r4,3)
576
577 antennaLat = obs_headElem_r(obsSpaceData,OBS_LAT,headerIndex)
578 antennaLon = obs_headElem_r(obsSpaceData,OBS_LON,headerIndex)
579 if (antennaLon < 0.0d0 ) antennaLon = antennaLon + 2.0d0*MPC_PI_R8
580 if (antennaLon >= 2.0d0*MPC_PI_R8) antennaLon = antennaLon - 2.0d0*MPC_PI_R8
581
582 beamElevation = obs_headElem_r(obsSpaceData, OBS_RELE, headerIndex)
583 beamAzimuth = obs_headElem_r(obsSpaceData, OBS_RZAM, headerIndex)
584 radarAltitude = obs_headElem_r(obsSpaceData, OBS_ALT, headerIndex)
585 beamRangeStart = obs_headElem_r(obsSpaceData, OBS_RANS, headerIndex)
586 beamRangeEnd = obs_headElem_r(obsSpaceData, OBS_RANE, headerIndex)
587
588 ! put the last antennaLat/antennaLon at the surface of thermo level
589 latSlantLev_T(nlev_T) = antennaLat
590 lonSlantLev_T(nlev_T) = antennaLon
591 ! Loop through rest of thermo level
592 do lev_T = 1, nlev_T-1
593 call findIntersectLatlonRadar(antennaLat, antennaLon, beamElevation, beamAzimuth, radarAltitude, & !in
594 beamRangeStart, beamRangeEnd, hco, height3D_T_r4(:,:,lev_T), & !in
595 latSlant, lonSlant) !out
596 latSlantLev_T(lev_T) = latSlant
597 lonSlantLev_T(lev_T) = lonSlant
598 end do
599
600 ! put the last antennaLat/beamlon at the surface of momentum levels
601 latSlantLev_M(nlev_M) = antennaLat
602 lonSlantLev_M(nlev_M) = antennaLon
603 ! loop through rest of momentum levels
604 do lev_M = 1, nlev_M-1
605 ! find the intersection
606 call findIntersectLatlonRadar(antennaLat, antennaLon , beamElevation, beamAzimuth, radarAltitude, & !in
607 beamRangeStart, beamRangeEnd, hco, height3D_M_r4(:,:,lev_M), & !in
608 latSlant, lonSlant) !out
609 latSlantLev_M(lev_M) = latSlant
610 lonSlantLev_M(lev_M) = lonSlant
611 end do
612
613 latSlantLev_S = antennaLat
614 lonSlantLev_S = antennaLon
615
616 end subroutine slp_calcLatLonRadar
617
618 subroutine findIntersectLatlonRadar(antennaLat, antennaLon, beamElevation, beamAzimuth, radarAltitude, & !in
619 beamRangeStart, beamRangeEnd, hco, field2d_height, & !in
620 latSlant, lonSlant) !out
621 !
622 !:Purpose: Computation of lat lon of the intersection
623 ! of radar beam with the levels model
624 !
625 !:NOTE: Bisection method
626 !
627 implicit none
628
629 ! Arguments:
630 type(struct_hco), intent(in) :: hco
631 real(8), intent(in) :: antennaLat
632 real(8), intent(in) :: antennaLon
633 real(8), intent(in) :: beamElevation
634 real(8), intent(in) :: beamAzimuth
635 real(8), intent(in) :: radarAltitude
636 real(8), intent(in) :: beamRangeStart
637 real(8), intent(in) :: beamRangeEnd
638 real(4), intent(in) :: field2d_height(hco%ni,hco%nj)
639 real(8), intent(out) :: latSlant
640 real(8), intent(out) :: lonSlant
641
642 ! Locals:
643 real(8) :: upper_bound, lower_bound, tolerance
644 real(8) :: beamHeight, mid, difference_heights, beamDistance
645 integer :: iteration,maximum_iteration
646 real(4) :: heightInterp_r4
647
648 lower_bound = beamRangeStart ! m max useable range for weather radars
649 upper_bound = beamRangeEnd ! m minimum range
650 tolerance = 1 ! tolerance of the bisection method
651 iteration = 0
652 maximum_iteration = 30
653 ! bisection method
654 mid=0.
655 do while ((abs((upper_bound - lower_bound)/2.)>tolerance).or.(iteration>maximum_iteration))
656 mid = (upper_bound + lower_bound)/2
657 call rdv_getlatlonHRfromRange(antennaLat, antennaLon, beamElevation, beamAzimuth, & !in
658 radarAltitude, mid, & !in
659 latSlant, lonSlant, beamHeight, beamDistance) !out
660 call heightBilinearInterp(latSlant, lonSlant, hco, field2d_height, heightInterp_r4)
661 difference_heights = beamHeight - heightInterp_r4
662 if (difference_heights>0.) then
663 upper_bound = mid
664 else
665 lower_bound = mid
666 end if
667 iteration = iteration+1
668 end do
669
670 if (lonSlant < 0.0d0 ) lonSlant = lonSlant + 2.0d0*MPC_PI_R8
671 if (lonSlant >= 2.0d0*MPC_PI_R8) lonSlant = lonSlant - 2.0d0*MPC_PI_R8
672
673 end subroutine findIntersectLatlonRadar
674
675end module slantProfileLatLon_mod