1 subroutine da_read_obs_chem_sfc (iv, filename, grid)
3 !-------------------------------------------------------------------------
4 ! Purpose: Read surface chem IC observation files
6 ! History: 03/2019 Creation (Wei Sun)
7 ! 06/2021 Add maximum thresholds for each species (Soyoung Ha)
8 ! Caution: chem_cv_options == 10 or 20 assumes all six species in [ug/m3]
9 ! chem_cv_options == 108 assumes pm25 and pm10 in [ug/m3], but
10 ! so2, no2, o3, co in [ppmv]
11 !-------------------------------------------------------------------------
15 type (iv_type), intent(inout) :: iv
16 character(len=*), intent(in) :: filename
17 type(domain), intent(in) :: grid ! first guess state.
19 ! chem_cv_options == 108: Maximum thresholds for observation values - treat as missing above these values
20 ! FIXME: We may want to put these maximum thresholds in namelist later.
21 real, dimension(6) :: max_pm = (/500.,999.,2.0, 2.0, 2.0, 50./) ! [ug/m3, ug/m3, ppmv, ppmv, ppmv, ppmv]
22 real :: pm25, pm10, so2, no2, o3, co
24 character (len = 6) :: SiteID
25 character (len = 10) :: fmt_name
27 character (len = 160) :: info_string
29 integer :: i, j, iost, nlevels, old_nlevels, fm,iunit
31 real :: dattmp,lattmp,lontmp
33 type (singl_level_type) :: platform
35 logical :: outside_all
36 integer :: surface_level
37 real :: height_error, u_comp, v_comp
38 integer :: nlocal(num_ob_indexes)
39 integer :: ilocal(num_ob_indexes)
40 integer :: ntotal(num_ob_indexes)
42 integer :: ndup, n, report, obs_index
44 real*8 :: obs_time, analysis_time
45 integer :: iyear, imonth, iday, ihour, imin, isite
46 real :: tdiff, dlat_earth,dlon_earth,crit
47 integer :: itt,itx,iout
48 real, allocatable :: in(:), out(:)
49 logical :: found, iuse, thin_3d, is_surface
50 integer :: i1,j1,k, levs
51 real :: dx,dy,dxm,dym,zk
52 real :: v_p(kms:kme),v_h(kms:kme)
54 if (trace_use) call da_trace_entry("da_read_obs_chem_sfc")
57 ntotal(:) = iv%info(:)%ptotal(iv%time-1)
58 nlocal(:) = iv%info(:)%plocal(iv%time-1)
61 if ( thin_conv_ascii ) then
62 do n = 1, num_ob_indexes
63 if ( n == radar ) cycle
64 call cleangrids_conv(n)
71 call da_get_unit(iunit)
73 FILE = trim(filename), &
75 ACCESS = 'SEQUENTIAL', &
80 write(unit=message(1),fmt='(A,I5,A)') &
81 "Error",iost," opening chem obs file "//trim(filename)
82 call da_warning(__FILE__,__LINE__,message(1:1))
83 call da_free_unit(iunit)
84 if (trace_use) call da_trace_exit("da_read_obs_chem_sfc")
88 allocate ( platform%chem (num_chemic_surf) )
89 obs_index = chemic_surf
97 report = 0 ! report number in file
105 ! =============================
106 platform%chem(:)%inv = missing_r
107 platform%chem(:)%qc = missing
108 platform%chem(:)%error = missing_r
110 ! read station general info
111 ! =============================
113 if (chem_cv_options == 10) then !!!! ium data !!!!
114 read (iunit, *, iostat = iost) &
115 iyear, imonth, iday, ihour, imin, &
118 platform%chem(PARAM_FIRST_SCALAR)%inv !!! pm25 only !!!
119 write (platform%info%date_char,'(i4,3(i2))') iyear, imonth, iday, ihour
120 platform%info%id = "333"
122 if (platform%chem(PARAM_FIRST_SCALAR)%inv==-999.or.platform%chem(PARAM_FIRST_SCALAR)%inv>=500) then
123 platform%chem(PARAM_FIRST_SCALAR)%inv=missing_r
125 platform%chem(PARAM_FIRST_SCALAR)%qc=333
128 else if (chem_cv_options == 20) then
129 if (chemicda_opt == 1) then
130 read (iunit, *, iostat = iost) &
131 iyear, imonth, iday, ihour, imin, &
134 platform%chem(PARAM_FIRST_SCALAR)%inv !!! pm25 only !!!
135 else if (chemicda_opt == 2) then
136 read (iunit, *, iostat = iost) &
137 iyear, imonth, iday, ihour, imin, &
140 platform%chem(PARAM_FIRST_SCALAR)%inv, & !!! pm25 read !!!
141 platform%chem(PARAM_FIRST_SCALAR)%inv !!! pm10 only !!!
142 else if (chemicda_opt == 3) then
143 read (iunit, *, iostat = iost) &
144 iyear, imonth, iday, ihour, imin, &
147 platform%chem(PARAM_FIRST_SCALAR)%inv, &
148 platform%chem(PARAM_FIRST_SCALAR+1)%inv !!! pm25 & pm10 !!!
149 else if (chemicda_opt == 4) then
150 read (iunit, *, iostat = iost) &
151 iyear, imonth, iday, ihour, imin, &
154 platform%chem(PARAM_FIRST_SCALAR)%inv, &
155 platform%chem(PARAM_FIRST_SCALAR)%inv, &
156 platform%chem(PARAM_FIRST_SCALAR)%inv, & !!! so2 !!!
157 platform%chem(PARAM_FIRST_SCALAR+1)%inv, & !!! no2 !!!
158 platform%chem(PARAM_FIRST_SCALAR+2)%inv, & !!! o3 !!!
159 platform%chem(PARAM_FIRST_SCALAR+3)%inv !!! co !!!
161 platform%chem(PARAM_FIRST_SCALAR+3)%inv = platform%chem(PARAM_FIRST_SCALAR+3)%inv*1000 !!! mg to ug !!!
163 else if (chemicda_opt == 5) then
164 read (iunit, *, iostat = iost) &
165 iyear, imonth, iday, ihour, imin, &
168 platform%chem(PARAM_FIRST_SCALAR)%inv, & !!! pm25 !!!
169 platform%chem(PARAM_FIRST_SCALAR+1)%inv, & !!! pm10 !!!
170 platform%chem(PARAM_FIRST_SCALAR+2)%inv, & !!! so2 !!!
171 platform%chem(PARAM_FIRST_SCALAR+3)%inv, & !!! no2 !!!
172 platform%chem(PARAM_FIRST_SCALAR+4)%inv, & !!! o3 !!!
173 platform%chem(PARAM_FIRST_SCALAR+5)%inv !!! co !!!
175 platform%chem(PARAM_FIRST_SCALAR+5)%inv = platform%chem(PARAM_FIRST_SCALAR+5)%inv*1000 !!! mg to ug !!!
178 write (platform%info%date_char,'(i4,3(i2))') iyear, imonth, iday, ihour
179 platform%info%id = "333"
181 if (platform%chem(PARAM_FIRST_SCALAR)%inv==-999.or.platform%chem(PARAM_FIRST_SCALAR)%inv>=500) then
182 platform%chem(PARAM_FIRST_SCALAR)%inv=missing_r
184 platform%chem(PARAM_FIRST_SCALAR)%qc=333
187 if (chemicda_opt == 3) then
188 if (platform%chem(PARAM_FIRST_SCALAR+1)%inv==-999.or.platform%chem(PARAM_FIRST_SCALAR+1)%inv>=500.or.platform%chem(PARAM_FIRST_SCALAR)%inv==-999.or.platform%chem(PARAM_FIRST_SCALAR+1)%inv.lt.platform%chem(PARAM_FIRST_SCALAR)%inv) then
189 platform%chem(PARAM_FIRST_SCALAR+1)%inv=missing_r
191 platform%chem(PARAM_FIRST_SCALAR+1)%inv=platform%chem(PARAM_FIRST_SCALAR+1)%inv-platform%chem(PARAM_FIRST_SCALAR)%inv
192 platform%chem(PARAM_FIRST_SCALAR+1)%qc=333
195 else if (chemicda_opt == 4) then
196 do ichem = PARAM_FIRST_SCALAR+1, num_chemic_surf !!! PARAM_FIRST_SCALAR+1 to PARAM_FIRST_SCALAR+3 !!!
197 if (ichem.eq.num_chemic_surf) then
198 if (platform%chem(ichem)%inv==-999.or.platform%chem(ichem)%inv>=500*1000) then
199 platform%chem(ichem)%inv=missing_r
201 platform%chem(ichem)%qc=333
204 if (platform%chem(ichem)%inv==-999.or.platform%chem(ichem)%inv>=500) then
205 platform%chem(ichem)%inv=missing_r
207 platform%chem(ichem)%qc=333
212 else if (chemicda_opt == 5) then
213 do ichem = PARAM_FIRST_SCALAR+1, num_chemic_surf !!! PARAM_FIRST_SCALAR+1 to PARAM_FIRST_SCALAR+5 !!!
214 if (ichem.eq.num_chemic_surf) then
215 if (platform%chem(ichem)%inv==-999.or.platform%chem(ichem)%inv>=500*1000) then
216 platform%chem(ichem)%inv=missing_r
218 platform%chem(ichem)%qc=333
220 else if (ichem.eq.PARAM_FIRST_SCALAR+1) then
221 if (platform%chem(PARAM_FIRST_SCALAR+1)%inv==-999.or.platform%chem(PARAM_FIRST_SCALAR+1)%inv>=500.or.platform%chem(PARAM_FIRST_SCALAR)%inv==-999.or.platform%chem(PARAM_FIRST_SCALAR+1)%inv.lt.platform%chem(PARAM_FIRST_SCALAR)%inv) then
222 platform%chem(PARAM_FIRST_SCALAR+1)%inv=missing_r
224 platform%chem(PARAM_FIRST_SCALAR+1)%inv=platform%chem(PARAM_FIRST_SCALAR+1)%inv-platform%chem(PARAM_FIRST_SCALAR)%inv
225 platform%chem(PARAM_FIRST_SCALAR+1)%qc=333
228 if (platform%chem(ichem)%inv==-999.or.platform%chem(ichem)%inv>=500) then
229 platform%chem(ichem)%inv=missing_r
231 platform%chem(ichem)%qc=333
238 else if (chem_cv_options == 108) then
240 read (iunit, fmt='(A6,F10.6,1X,F10.6,1X,I4,3I2,2F9.1,4F9.3)', iostat=iost) &
244 iyear, imonth, iday, ihour, &
245 pm25, pm10, so2, no2, o3, co
247 platform%info%id = trim(SiteID)
248 read(SiteID,'(I6)') isite
249 write (platform%info%date_char,'(i4,3(i2.2))') iyear, imonth, iday, ihour
252 ! Caution: Observations are assumed to have the same unit of ppmv.
253 ! Unit conversion (to ppmv) must have been done in the preprocessing.
254 ! (ex. The original chinese data had ug/m3 for gas species.)
257 select case ( chemicda_opt )
259 platform%chem(PARAM_FIRST_SCALAR )%inv = pm25
260 platform%info%name = "pm25"
262 platform%chem(PARAM_FIRST_SCALAR )%inv = pm10
263 platform%info%name = "pm10"
265 platform%chem(PARAM_FIRST_SCALAR )%inv = pm25
266 platform%chem(PARAM_FIRST_SCALAR+1)%inv = pm10
267 platform%info%name = "all_pm"
269 platform%chem(PARAM_FIRST_SCALAR )%inv = so2
270 platform%chem(PARAM_FIRST_SCALAR+1)%inv = no2
271 platform%chem(PARAM_FIRST_SCALAR+2)%inv = o3
272 platform%chem(PARAM_FIRST_SCALAR+3)%inv = co
273 platform%info%name = "gas"
275 platform%chem(PARAM_FIRST_SCALAR )%inv = pm25
276 platform%chem(PARAM_FIRST_SCALAR+1)%inv = pm10
277 platform%chem(PARAM_FIRST_SCALAR+2)%inv = so2
278 platform%chem(PARAM_FIRST_SCALAR+3)%inv = no2
279 platform%chem(PARAM_FIRST_SCALAR+4)%inv = o3
280 platform%chem(PARAM_FIRST_SCALAR+5)%inv = co
281 platform%info%name = "all"
283 platform%chem(PARAM_FIRST_SCALAR )%inv = pm25
284 platform%info%name = ""
289 write(*,'(A,6f7.1)') &
290 "da_read_obs_chem_sfc: pm25, pm10 [ug/m3], so2, no2, o3, co [ppmv] obs are rejected if >= ", max_pm
291 do ichem = PARAM_FIRST_SCALAR, num_chemic_surf
292 ipm = ichem - PARAM_FIRST_SCALAR + 1
293 if (platform%chem(ichem)%inv<0.or.abs(platform%chem(ichem)%inv)>=max_pm(ipm)) then
294 platform%chem(ichem)%inv=missing_r
296 platform%chem(ichem)%qc = 0
300 if ((chemicda_opt == 3) .or. (chemicda_opt == 5)) then
301 if ((platform%chem(PARAM_FIRST_SCALAR+1)%inv.lt.platform%chem(PARAM_FIRST_SCALAR)%inv) .or. &
302 (platform%chem(PARAM_FIRST_SCALAR+1)%inv<0).or.(platform%chem(PARAM_FIRST_SCALAR)%inv<0)) then ! Feb-27-2022
303 platform%chem(PARAM_FIRST_SCALAR+1)%inv = missing_r
304 platform%chem(PARAM_FIRST_SCALAR+1)%qc = missing
306 ! pm10 <= pm10 - pm25 residual
307 platform%chem(PARAM_FIRST_SCALAR+1)%inv = platform%chem(PARAM_FIRST_SCALAR+1)%inv - &
308 platform%chem(PARAM_FIRST_SCALAR)%inv
310 end if ! ((chemicda_opt == 3) .or. (chemicda_opt == 5)) then
312 end if ! if (chem_cv_options == 108)
315 ! FIX? This is expected, but its unclear how we handle failure
316 ! here without assuming the fortran2003 convention on
321 if (platform%info%lon == 180.0 ) platform%info%lon =-180.000
322 ! Fix funny wind direction at Poles
323 if (platform%info%lat < -89.9999 .or. platform%info%lat > 89.9999) then
324 platform%info%lon = 0.0
327 ! read model location
328 ! =========================
330 ! Check if outside of the time range:
332 call da_get_julian_time (iyear,imonth,iday,ihour,imin,obs_time)
333 if ( obs_time < time_slots(0) .or. &
334 obs_time >= time_slots(num_fgat_time) ) then
335 if (print_detail_obs) then
336 write(unit=stdout, fmt='(a)') '*** Outside of the time range:'
337 write(unit=stdout, fmt=fmt_info) &
338 platform%info%platform, &
339 platform%info%date_char, &
340 platform%info%name, &
341 platform%info%levels, &
350 ! Restrict to a range of reports, useful for debugging
352 if (report < report_start) then
356 if (report > report_end) then
360 call da_llxy (platform%info, platform%loc, outside, outside_all)
362 if (outside_all) then
366 if (print_detail_obs) then
367 ! Simplistic approach, could be improved to get it all done on PE 0
368 if (.NOT. outside) then
369 write(unit=stdout,fmt='(A,I5,A,A,3X,2F8.2,2I4,2F7.1,4F9.3)') &
370 "Report",report," at ",trim(platform%info%id),platform%info%lat,platform%info%lon, &
371 platform%loc%j, platform%loc%i, pm25, pm10, so2, no2, o3, co
372 !write(unit=stdout,fmt='(A,I5,A,F8.2,A,F8.2,A,I3,A,2F8.2)') &
373 ! "Report",report," at",platform%info%lon,"E",platform%info%lat, &
374 ! "N on processor", myproc,", position", platform%loc%x,platform%loc%y
378 call da_get_julian_time (iyear,imonth,iday,ihour,imin,analysis_time)
379 tdiff = abs((obs_time - analysis_time)-0.02)
380 dlat_earth = platform%info%lat
381 dlon_earth = platform%info%lon
382 if (dlon_earth < 0.0) dlon_earth = dlon_earth + 360.0
383 if (dlon_earth >= 360.0) dlon_earth = dlon_earth - 360.0
384 dlat_earth = dlat_earth * deg2rad
385 dlon_earth = dlon_earth * deg2rad
389 ! Loop over duplicating obs for global
391 if (global .and. (platform%loc%i < ids .or. platform%loc%i >= ide)) ndup= 2
393 ! It is possible that logic for counting obs is incorrect for the
394 ! global case with >1 MPI tasks due to obs duplication, halo, etc.
397 if (.not.outside) then
398 if (print_detail_obs .and. ndup > 1) then
399 write(unit=stdout, fmt = fmt_info) &
400 platform%info%platform, &
401 platform%info%date_char, &
402 platform%info%name, &
403 platform%info%levels, &
409 write(unit=stdout, fmt = '(a,2i5,4f10.3)') &
410 ' duplicating obs since loc% i,j,dx,dxm,dy & dym ', &
411 platform%loc%i, platform%loc%j, &
412 platform%loc%dx, platform%loc%dxm, &
413 platform%loc%dy, platform%loc%dym
417 dup_loop: do n = 1, ndup
419 if (use_chemic_surfobs) then
420 if ( ntotal(chemic_surf) == max_synop_input ) cycle reports
421 if (n==1) ntotal(chemic_surf) = ntotal(chemic_surf)+1
422 if (outside) cycle reports
423 if ( thin_conv_ascii ) then
425 call map2grids_conv(chemic_surf,dlat_earth,dlon_earth,crit,nlocal(chemic_surf),itx,1,itt,ilocal(chemic_surf),iuse)
426 if ( .not. iuse ) cycle reports
428 nlocal(chemic_surf) = nlocal(chemic_surf) + 1
429 ilocal(chemic_surf) = ilocal(chemic_surf) + 1
432 allocate ( iv%chemic_surf(ilocal(chemic_surf))%chem(num_chemic_surf))
434 do ichem = PARAM_FIRST_SCALAR, num_chemic_surf
435 iv%chemic_surf(ilocal(chemic_surf))%chem(ichem) = platform%chem(ichem)
440 write(unit=message(1), fmt='(a)') 'unsaved obs found:'
441 write(unit=message(2), fmt='(2a)') &
442 'platform%info%platform=', platform%info%platform
443 write(unit=message(3), fmt='(a, i3)') &
444 'platform%info%levels=', platform%info%levels
445 call da_warning(__FILE__,__LINE__,message(1:3))
450 if( is_surface .or. (obs_index == gpspw) .or. (levs > 0 .and. .not. thin_conv_ascii) .or. &
451 (levs > 0 .and. (thin_conv_ascii .and. (obs_index /= airep .and. obs_index /= tamdar))) ) then
452 iv%info(obs_index)%name(ilocal(obs_index)) = platform%info%name
453 iv%info(obs_index)%platform(ilocal(obs_index)) = platform%info%platform
454 iv%info(obs_index)%id(ilocal(obs_index)) = platform%info%id
455 iv%info(obs_index)%date_char(ilocal(obs_index)) = platform%info%date_char
456 ! nlevels adjusted for some obs types so use that
457 iv%info(obs_index)%levels(ilocal(obs_index)) = min(iv%info(obs_index)%max_lev, levs)
458 iv%info(obs_index)%lat(:,ilocal(obs_index)) = platform%info%lat
459 iv%info(obs_index)%lon(:,ilocal(obs_index)) = platform%info%lon
460 iv%info(obs_index)%elv(ilocal(obs_index)) = platform%info%elv
461 iv%info(obs_index)%pstar(ilocal(obs_index)) = platform%info%pstar
463 iv%info(obs_index)%slp(ilocal(obs_index)) = platform%loc%slp
464 iv%info(obs_index)%pw(ilocal(obs_index)) = platform%loc%pw
465 iv%info(obs_index)%x(:,ilocal(obs_index)) = platform%loc%x
466 iv%info(obs_index)%y(:,ilocal(obs_index)) = platform%loc%y
467 iv%info(obs_index)%i(:,ilocal(obs_index)) = platform%loc%i
468 iv%info(obs_index)%j(:,ilocal(obs_index)) = platform%loc%j
469 iv%info(obs_index)%dx(:,ilocal(obs_index)) = platform%loc%dx
470 iv%info(obs_index)%dxm(:,ilocal(obs_index)) = platform%loc%dxm
471 iv%info(obs_index)%dy(:,ilocal(obs_index)) = platform%loc%dy
472 iv%info(obs_index)%dym(:,ilocal(obs_index)) = platform%loc%dym
473 iv%info(obs_index)%proc_domain(:,ilocal(obs_index)) = platform%loc%proc_domain
475 iv%info(obs_index)%obs_global_index(nlocal(obs_index)) = ntotal(obs_index)
477 end if ! for thin_conv_ascii skipping obs_index for which thin_3d is true like airep and tamdir
479 if (global .and. n < 2) then
480 if (test_transforms) exit dup_loop
481 if (platform%loc % i >= ide) then
482 platform%loc%i = platform%loc % i - ide
483 else if (platform%loc % i < ids) then
484 platform%loc%i = platform%loc % i + ide
487 platform%loc%proc_domain = .not. platform%loc%proc_domain
495 call da_free_unit(iunit)
498 if ( thin_conv_ascii ) then
499 print*, 'da_read_obs_chem_sfc: thin_conv_ascii ',thin_conv_ascii
500 do n = 1, num_ob_indexes
502 allocate ( in(thinning_grid_conv(n)%itxmax) )
503 allocate (out(thinning_grid_conv(n)%itxmax) )
504 do i = 1, thinning_grid_conv(n)%itxmax
505 in(i) = thinning_grid_conv(n)%score_crit(i)
508 ! Get minimum crit and associated processor index.
509 call mpi_reduce(in, out, thinning_grid_conv(n)%itxmax, true_mpi_real, mpi_min, root, comm, ierr)
510 call wrf_dm_bcast_real (out, thinning_grid_conv(n)%itxmax)
514 do i = 1, thinning_grid_conv(n)%itxmax
515 if( out(i) < 9.99e6) iv%info(n)%thin_ntotal= iv%info(n)%thin_ntotal + 1
516 if ( abs(out(i)-thinning_grid_conv(n)%score_crit(i)) > 1.0E-10 ) then
517 thinning_grid_conv(n)%ibest_obs(i) = 0
520 ! do j = iv%info(n)%plocal(iv%time -1)+1 , iv%info(n)%plocal(iv%time -1)+nlocal(n)
521 do j = iv%info(n)%plocal(iv%time -1)+1 , nlocal(n)
523 do i = 1, thinning_grid_conv(n)%itxmax
524 if ( thinning_grid_conv(n)%ibest_obs(i) == j .and. &
525 thinning_grid_conv(n)%score_crit(i) < 9.99e6 ) then
526 iv%info(n)%thin_nlocal = iv%info(n)%thin_nlocal + 1
532 if ( .not. found ) then
533 iv%info(n)%thinned(:,j) = .true.
538 end do ! loop over num_ob_indexes
539 end if ! thin_conv_ascii
541 if (trace_use) call da_trace_exit("da_read_obs_chem_sfc")
543 end subroutine da_read_obs_chem_sfc