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程式說明
引數
- grb2D1m3.py YYMM.nc
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
- 讀取合併後之EAC4檔案
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_gas
和nms_part
的對照表挑選污染項目。 - 先對時間進行展開。由於模版污染項目總數即為
nms_gas
和nms_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模組進行(線性)內插。
- EAC4的高度方向自頂到底、緯度自北向南,與
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等項目。
結果檢視
程式下載
Download: grb2D1m3.py
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