Link Search Menu Expand Document

EAC4檔案轉成5階m3.nc

Table of contents

背景

  • 歐洲中期天氣預報中心(ECMWF)之EAC4 (ECMWF Atmospheric Composition Reanalysis 4)數據下載整併後,此處將其轉成m3.nc檔案,以供VERDI等顯示軟體、以及後續光化模式所需。
  • 由於EAC4粒狀物單位(重量混合比)轉換過程需要大氣的密度,不單是高度的函數,也隨著天氣系統而有時空的變化。可以由mcip計算結果(METCRO3D)中讀取,需在執行轉換前預備好。

逐日密度檔案之準備

  • 由於此處EAC4檔案的時間範圍為全月,而mcip結果是彼此會有重疊的批次作業,因此需以brk_day.cs拆解、讓程式可以逐日讀取,以降低複雜度。程序如下:
    • 先以ncks讀取METCRO3D檔案中的密度(DENS)及時間標籤
    • 再以brk_day.cs拆解成逐日檔案備用
    • 密度單位kg/M3,此處未更動,在主程式中進行轉換。
for r in {5..12};do 
  nc=/nas1/cmaqruns/2018base/data/mcip/1804_run$r/sChina_81ki/METCRO3D_1804_run$r.nc
  ncks -O -v TFLAG,DENS $nc RHO.1804.nc;
  brk_day2.cs RHO.1804.nc
done
ls 1804
RHO.20180331.nc  RHO.20180405.nc  RHO.20180410.nc  RHO.20180415.nc  RHO.20180420.nc  RHO.20180425.nc  RHO.20180430.nc  RHO.20211222.nc
...
RHO.20180404.nc  RHO.20180409.nc  RHO.20180414.nc  RHO.20180419.nc  RHO.20180424.nc  RHO.20180429.nc  RHO.20211221.nc
  • Note:brk_day2.cs的引數必須以YYMM做為主檔名的最後標籤(如範例中的RHO.1804.nc)。

grb2D1m3.py程式說明

引數

I/O檔案

  • Inputs
    • YYMM.nc
    • BCON模版
    • m3.nc模版
    • 逐日密度檔案
  • JSON’s
    • dic.json={eac4污染物代碼:污染物名稱}
    • mws.json={污染物名稱:分子量}
    • nms_gas.json:{eac4污染物代碼:CMAQ污染物名稱}
    • nms_part.json:{eac4污染物代碼:CMAQ污染物名稱序列}
  • Output
    • YYMMD1.m3.nc

dic.json

{ 
  "VAR_192_210_123_P0_L105_GLL0":"carbon_monoxide", 
  "VAR_192_217_45_P0_L105_GLL0":"ethane", 
  "VAR_192_210_124_P0_L105_GLL0":"formaldehyde", 
  "VAR_192_217_16_P0_L105_GLL0":"isoprene", 
  "VAR_192_210_121_P0_L105_GLL0":"nitrogen_dioxide", 
  "VAR_192_217_27_P0_L105_GLL0":"nitrogen_monoxide", 
  "VAR_192_217_11_P0_L105_GLL0":"olefins", 
  "VAR_192_217_6_P0_L105_GLL0":"nitric_acid", 
  "VAR_192_217_15_P0_L105_GLL0":"organic_nitrates", 
  "VAR_192_210_203_P0_L105_GLL0":"ozone", 
  "VAR_192_217_9_P0_L105_GLL0":"paraffins", 
  "VAR_192_217_13_P0_L105_GLL0":"peroxyacetyl_nitrate", 
  "VAR_192_217_47_P0_L105_GLL0":"propane", 
  "VAR_192_210_122_P0_L105_GLL0":"sulphur_dioxide"
  "MASSMR_P40_L105_GLL0_A0":"ozone",
  "MASSMR_P40_L105_GLL0_A4":"carbon_monoxide",
  "MASSMR_P40_L105_GLL0_A5":"nitrogen_dioxide",
  "MASSMR_P40_L105_GLL0_A8":"sulphur_dioxide",
  "MASSMR_P40_L105_GLL0_A11":"nitrogen_monoxide",

  "VAR_192_217_21_P0_L105_GLL0":"ammonium", 
  "VAR_192_210_4_P0_L105_GLL0":"dust_aerosol_0.03-0.55um_mixing_ratio", 
  "VAR_192_210_5_P0_L105_GLL0":"dust_aerosol_0.55-0.9um_mixing_ratio", 
  "VAR_192_210_6_P0_L105_GLL0":"dust_aerosol_0.9-20um_mixing_ratio", 
  "VAR_192_210_9_P0_L105_GLL0":"hydrophilic_black_carbon_aerosol_mixing_ratio", 
  "VAR_192_210_7_P0_L105_GLL0":"hydrophilic_organic_matter_aerosol_mixing_ratio", 
  "VAR_192_210_10_P0_L105_GLL0":"hydrophobic_black_carbon_aerosol_mixing_ratio", 
  "VAR_192_210_8_P0_L105_GLL0":"hydrophobic_organic_matter_aerosol_mixing_ratio", 
  "VAR_192_217_51_P0_L105_GLL0":"nitrate", 
  "VAR_192_210_1_P0_L105_GLL0":"sea_salt_aerosol_0.03-0.5um_mixing_ratio", 
  "VAR_192_210_2_P0_L105_GLL0":"sea_salt_aerosol_0.5-5um_mixing_ratio", 
  "VAR_192_210_3_P0_L105_GLL0":"sea_salt_aerosol_5-20um_mixing_ratio", 
  "VAR_192_210_11_P0_L105_GLL0":"sulphate_aerosol_mixing_ratio", 
  "MASSMR_P48_L105_GLL0_A62003":"ammonium_aerosol_mass_mixing_ratio",
  "MASSMR_P48_L105_GLL0_A65533":"nitrate_coarse_mode_aerosol_mass_mixing_ratio",
  "MASSMR_P48_L105_GLL0_A65534":"nitrate_fine_mode_aerosol_mass_mixing_ratio",
  }

nms_part.json

  • dust_aerosol_0.03-0.55um為塵土中的PM1,但是CMAQ並沒有處理這一塊,為求質量守恆,將其併入PM2.5之中(J類別)
{
"VAR_192_217_21_P0_L105_GLL0": ["ANH4I", "ANH4J", "ANH4K"], 
"VAR_192_210_4_P0_L105_GLL0": ["AFEJ", "AALJ", "ASIJ", "ACAJ", "AMGJ", "AKJ", "AMNJ"], 
"VAR_192_210_5_P0_L105_GLL0": ["AFEJ", "AALJ", "ASIJ", "ACAJ", "AMGJ", "AKJ", "AMNJ"], 
"VAR_192_210_6_P0_L105_GLL0": ["ACORS", "ASOIL"], 
"VAR_192_210_9_P0_L105_GLL0": ["AECI", "AECJ"], 
"VAR_192_210_7_P0_L105_GLL0": ["APOCI", "APNCOMI", "APOCJ", "AOTHRJ", "AISO3J", "ASQTJ", "AORGCJ", "AOLGBJ", "AOLGAJ"], 
"VAR_192_210_10_P0_L105_GLL0": ["AECI", "AECJ"], 
"VAR_192_210_8_P0_L105_GLL0": ["APOCI", "APNCOMI", "APOCJ", "AOTHRJ", "AISO3J", "ASQTJ", "AORGCJ", "AOLGBJ", "AOLGAJ"], 
"VAR_192_217_51_P0_L105_GLL0": ["ANO3I", "ANO3J", "ANO3K"], 
"VAR_192_210_1_P0_L105_GLL0": ["ANAI", "ACLI", "ANAJ", "ACLJ"], 
"VAR_192_210_2_P0_L105_GLL0": ["ACLK", "ASEACAT"], 
"VAR_192_210_3_P0_L105_GLL0": [], 
"VAR_192_210_11_P0_L105_GLL0": ["ASO4I", "ASO4J", "ASO4K"],
"MASSMR_P48_L105_GLL0_A62003": ["ANH4I", "ANH4J", "ANH4K"],
"MASSMR_P48_L105_GLL0_A65533": ["ANO3K"],
"MASSMR_P48_L105_GLL0_A65534": ["ANO3I", "ANO3J"],
}

分段說明

  • 使用scipy的griddata進行水平的內插
  • 將dt2jul、jul2dt寫成副程式dtconvertor,簡化程式版面
  • 對照表也寫成json檔案
     1	#argumens numNest gribFname
     2	import netCDF4
     3	import numpy as np
     4	import datetime
     5	from scipy.interpolate import griddata
     6	import json
     7	
     8	from pyproj import Proj
     9	import sys,os,subprocess
    10	from dtconvertor import dt2jul, jul2dt
    11	
    12	
  • 分別讀取分子量(mws)、EAC4物質代碼(dic)、與對照到CMAQ氣狀物(nms_gas)及粒狀物(nms_part)之名稱
    • 層數的對照表(deprecated,改在wsite進行對照)
    13	for v in ['mws','dic','nms_gas','nms_part']:
    14	  with open(v+'.json', 'r') as jsonfile:
    15	    exec(v+'=json.load(jsonfile)')
    16	uts=['PPM',"ug m-3          "]
    17	l34=['21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', 
    18	     '38', '39', '40', '42', '43', '44', '46', '47', '48', '49', '50', '51', '53', '54', '56', '57', '59']
    19	l40=['21', '21', '22', '22', '23', '24', '24', '25', '25', '26', '27', '28', '28', '29', '30', '31', '32', '32', '33', '34', 
    20	     '35', '36', '37', '38', '39', '40', '42', '43', '44', '46', '47', '48', '49', '50', '51', '53', '54', '56', '57', '59']
    21	d40_23={39-k:l34.index(l40[k]) for k in range(40)}
  • 讀取一個BCON的樣版。從中取得粒狀物之間的比例關係(rate)
    • 因為EAC4的粒狀物沒有粒徑I、J之分,需要將其切分。
    22	byr=subprocess.check_output('pwd',shell=True).decode('utf8').strip('\n')[-2:]
    23	#read a BC file as rate base
    24	fname='/nas1/cmaqruns/2019base/data/bcon/BCON_v53_1912_run5_regrid_20191201_TWN_3X3'
    25	nc = netCDF4.Dataset(fname,'r')
    26	Vb=[list(filter(lambda x:nc.variables[x].ndim==j, [i for i in nc.variables])) for j in [1,2,3,4]]
    27	rate={}
    28	for v in nms_part:
    29	  nms=nms_part[v]
    30	  for nm in nms:
    31	    if nm not in Vb[2]:sys.exit(v+' not in BCON file')
    32	  avg=[np.mean(nc.variables[nm][:]) for nm in nms]
    33	  sum_avg=sum(avg)
    34	  if sum_avg==0:sys.exit('sum_avg==0')
    35	  ratev=[avg[i]/sum_avg for i in range(len(avg))]
    36	  rate.update({v:ratev})
    37	for v in nms_gas:
    38	  rate.update({v:[1.]})
    39	
    40	#read the merged grib files (ncl_convert2nc)
    41	#lay,row in reversed directions
    42	fname=sys.argv[1]
    43	nc = netCDF4.Dataset(fname,'r')
    44	V=[list(filter(lambda x:nc.variables[x].ndim==j, [i for i in nc.variables])) for j in [1,2,3,4]]
    45	nt,nlay,nrow,ncol=(nc.variables[V[3][0]].shape[i] for i in range(4))
    46	#read the timestamp in nc and store at /expand the nc1
    47	SDATE=[datetime.datetime.strptime(''.join([str(i, encoding='utf-8') for i in list(nc.variables[V[1][0]][t, :])]),\
    48	 '%m/%d/%Y (%H:%M)') for t in range(nt)]
    49	bdate,edate=SDATE[0],SDATE[-1]
    50	delt=edate-bdate
    51	ntA=int(delt.total_seconds()/3600.)
    52	JuliHr=[int((bdate+datetime.timedelta(hours=t)).strftime("%Y%j%H")) for t in range(ntA)]
    53	
  • 讀取m3.nc模版
    • 污染項目需事先按照nms_gasnms_part的對照表挑選污染項目。
    • 先對時間進行展開。由於模版污染項目總數即為nms_gasnms_part值的總數,因此不會有未給定而被遮蔽的情形,不必花時間將矩陣全部清空。
    54	N='D1'
    55	tmps={'D1':'templateD1.ncV49K34','D2':'templateD2.ncV49K34'}
    56	path='./'
    57	fnameO=fname.replace('.nc',N+'.m3.nc')
    58	if not os.path.exists(fnameO):os.system('cp '+path+tmps[N]+' '+fnameO)
    59	nc1= netCDF4.Dataset(fnameO,'r+')
    60	V1=[list(filter(lambda x:nc1.variables[x].ndim==j, [i for i in nc1.variables])) for j in [1,2,3,4]]
    61	nv1=len(V1[3])
    62	nt1,nlay1,nrow1,ncol1=nc1.variables[V1[3][0]].shape
    63	if nt1<ntA:
    64	  print('expand the matrix')
    65	  for t in range(ntA):
    66	    nc1.variables['TFLAG'][t,0,0]=0
    67	if nt1>ntA:
    68	  nc1.close()
    69	  ncks=subprocess.check_output('which ncks',shell=True).decode('utf8').strip('\n')
    70	  os.system(ncks+' -O -d TSTEP,0,'+str(ntA-1)+' '+fnameO+' '+fnameO+'_tmp;mv '+fnameO+'_tmp '+fnameO) 
    71	  nc1= netCDF4.Dataset(fnameO,'r+')
    72	nc1.SDATE=JuliHr[0]//100
    73	nc1.STIME=JuliHr[0]%100*10000
    74	var=np.zeros(shape=(ntA,nc1.NVARS,2))
    75	var[:,:,0]=np.array([i//100 for i in JuliHr])[:,None]
    76	var[:,:,1]=np.array([i%100  for i in JuliHr])[:,None]*10000
    77	nc1.variables['TFLAG'][:,:,:]=var[:,:,:]
  • 空間內插之準備
    • 因EAC4檔案為等經緯度、非直角座標系統,因此選擇以scipy.griddata方式內插
    78	Latitude_Pole, Longitude_Pole = 23.61000, 120.9900
    79	pnyc = Proj(proj='lcc', datum='NAD83', lat_1=10, lat_2=40,
    80	        lat_0=Latitude_Pole, lon_0=Longitude_Pole, x_0=0, y_0=0.0)
    81	
    82	xlon, xlat = nc.variables['lon_0'][:].flatten(), np.flip(nc.variables['lat_0'][:].flatten())
    83	lonm, latm = np.meshgrid(xlon, xlat)
    84	x,y=pnyc(lonm,latm, inverse=False)
    85	
    86	#interpolation indexing
    87	x1d=[nc1.XORIG+nc1.XCELL*i for i in range(ncol1)]
    88	y1d=[nc1.YORIG+nc1.YCELL*i for i in range(nrow1)]
    89	x1,y1=np.meshgrid(x1d, y1d)
    90	maxx,maxy=x1[-1,-1],y1[-1,-1]
    91	minx,miny=x1[0,0],y1[0,0]
    92	boo=(abs(x) <= (maxx - minx) /2+nc1.XCELL*10) & (abs(y) <= (maxy - miny) /2+nc1.YCELL*10)
    93	idx = np.where(boo)
    94	mp=len(idx[0])
    95	xyc= [(x[idx[0][i],idx[1][i]],y[idx[0][i],idx[1][i]]) for i in range(mp)]
    96	
  • 讀取[空氣密度]數據,以備粒狀物濃度的計算
    97	print('read the density of air')
    98	dlay=np.array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
    99	       17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 30, 32, 34, 35, 37, 39])
   100	dens=np.zeros(shape=(ntA,40, nrow1, ncol1))
   101	caldat=list(set([int((bdate+datetime.timedelta(hours=t)).strftime("%Y%m%d")) for t in range(ntA)]))
   102	caldat.sort()
   103	for c in caldat:
   104	  iday=caldat.index(c)
   105	  fname='/nas1/cmaqruns/20'+byr+'base/data/mcip/RHO/RHO.'+str(c)+'.nc'
   106	  ncr = netCDF4.Dataset(fname,'r')
   107	  ntr=min(24,ncr.dimensions['TSTEP'].size)
   108	  t1=iday*24
   109	  t2=min(ntA,t1+min(24,ntr))
   110	  hrs=t2-t1
   111	  dens[t1:t2,:,:,:]=ncr.variables['DENS'][:hrs,:,:,:] *1E9 #(kg to microgram)
   112	dens2=np.zeros(shape=(ntA,nlay1, nrow1, ncol1))
   113	for k in range(nlay1):
   114	  dens2[:,k,:,:]=dens[:,dlay[k],:,:]
  • 讀取EAC4濃度內容,進行空間內插
    • EAC4的高度方向自頂到底、緯度自北向南,與m3.nc定義相反,需要進行翻轉(np.flip),4個軸中高度緯度分別是第1、2軸。
    • 翻轉後矩陣、併同前述d01網格座標系統,一起輸入griddata模組進行(線性)內插。
   115	var=np.zeros(shape=(nt, nlay, nrow, ncol))
   116	zz=np.zeros(shape=(nt, nlay1, nrow1, ncol1))
   117	var2=np.zeros(shape=(ntA,nlay1, nrow1, ncol1))
   118	print('fill the matrix')
   119	for v in list(nms_gas)+list(nms_part) :
   120	  var[:,:,:,:]=np.flip(nc.variables[v][:,:,:,:], [1,2])
   121	  for t in range(nt):
   122	    c = np.array([var[t,:,idx[0][i], idx[1][i]] for i in range(mp)])
   123	    for k in range(nlay1):
   124	      zz[t,k,:,: ] = griddata(xyc, c[:,k], (x1, y1), method='linear')
  • 時間(線性)內插
   125	  for t in range(0,ntA,3):
   126	    t3=int(t/3)
   127	    var2[t+0,:,:,:]=zz[t3,:,:,:]
   128	    var2[t+1,:,:,:]=zz[t3,:,:,:]*2/3+zz[t3+1,:,:,:]*1/3
   129	    var2[t+2,:,:,:]=zz[t3,:,:,:]*1/3+zz[t3+1,:,:,:]*2/3
  • 氣狀物單位轉換(重量混合比轉PPM)
   130	  if v in nms_gas:
   131	    nc1.variables[nms_gas[v]][:]=var2[:]*rate[v][0] * 28.E6/mws[dic[v]] #mixing ratio to ppm
  • 粒狀物單位轉換(重量混合比轉 μ g/M3)
   132	  else:
   133	#    unit=1E9*dens[:] #28.E6/mvol #mixing ratio(kg/kg) to microgram/M3
   134	    nms=nms_part[v]
   135	    for nm in nms:
   136	      nc1.variables[nm][:]+=var2[:] * rate[v][nms.index(nm)] * dens2[:]
   137	  print(v)
   138	
  • 給定全域屬性,存檔,離開。
   139	nc1.SDATE,nc1.STIME=nc1.variables['TFLAG'][0,0,:]
   140	nc1.NLAYS=nlay1
   141	nc1.TSTEP=10000
   142	nc1.close()
   143	

m3.nc檔案之後處理(combine)

  • 複製一個完整批次的CCTM_ACONC檔案做為容器
  • 擷取全月結果中該批次的日期部分,將濃度值倒入容器,即可進行CMAQ的後處理combine.exe,整併VOCs、PM10、PM2.5等項目。

結果檢視

  • 2018/4/5~6 大陸沙塵暴之EAC4濃度水平分布mov
  • 2018/4/5~6 大陸沙塵暴之EAC4濃度垂直分布(臺灣為中心)mov

程式下載

Reference

  • ECMWF, EAC4 (ECMWF Atmospheric Composition Reanalysis 4), copernicus,record updated 2021-12-07 16:10:05 UTC
  • 純淨天空, python numpy flip用法及代碼示例, vimsky -Python学习园, Scipy Tutorial-多维插值griddata, cpython