Leed大學CEMAC中心建置了Masaya 火山噴發SO2/SO4造成地面濃度的預報模式,範例如圖所示。由於氣象預報數據、大氣擴散模式等皆為官方作業系統與優選模式,因此具有高度的參考價值。
此處將其進行本土化,應用於空氣品質預報、緊急應變作業系統中。
該系統自動下載未來48小時MAM氣象數值預報的結果,轉成CALMET的輸入檔,進入CALPUFF模式進行地面空氣品質的模擬。
由於CALPUFF模式具有考慮3D風場、地形變化、化學反應等模擬能力,曾被美國環保署列為大氣擴散中的優選模式,用於大型固定污染源原生污染物以及二次氣膠長程傳輸的模式。
系統工作流程由Run.sh批次檔所控制,重要節點及困難如下:
# Defaults that can be overwritten by editing HERE:
# Command line option m switches all to false
runTERREL=false (地形)
runCTGPROC=false(土地使用)
runMAKEGEO=false(綜合地理設定)
run3DDAT=false(下載WRF grib files)
runCALMET=false(執行calmet)
runCALPUFF=false(執行calpuff)
runmodel=true
![]() |
|---|
| leed大學火山SO2預報結果畫面 |
![]() |
| 台中電廠PM2.5預報結果畫面 |
![]() |
|---|
| CWB WRF 模式3KM預報結果風場 |
xml檔案內容內,範例如下:
https://opendata.cwb.gov.tw/fileapi/opendata/MIC/M-A006${dom}-0$i.grb2https://cwbopendata.s3.ap-northeast-1.amazonaws.com/MIC/M-A006${dom}-0$i.grb2wget(1.12)會需要加上選項--no-check-certificate![]() |
![]() |
|---|---|
| CWB_WRF 3維變數之高度分布 | CWB_WRF數值預報地面項目 |
for i in 000 006 012 018 024 030 036 042 048 054 060 066 072 078 084 ;do
wget https://opendata.cwb.gov.tw/fileapi/opendata/MIC/M-A0064-$i.grb2
done
conda activate ncl_stable
convert2nc M-A0064-000.grb2
Python nc2db.py#將每一個格點的座標寫成csv檔案。
import netCDF4
from pandas import *
import argparse
import numpy as np
parser = argparse.ArgumentParser(description="read NetCDF data and transform to DataFrame csvs. ",\
epilog="Example of use: pythonb nc2df NC_FILENAME")
parser.add_argument("nc_filename", help="nc filebname",type=str)
args = parser.parse_args()
fname = args.nc_filename
nc = netCDF4.Dataset(fname)
cols=list(nc.variables) #note 1
d={}
for c in ['gridlon_0','gridlat_0']:
a=list(np.array(nc.variables[c]).flatten()) #note 3
d.update({c: a})
df=DataFrame(d)
df.to_csv(fname+'.csv')
conda env create -f environment.ymlimport ModuleNotFoundError: No module named '_gribapi_swig'conda create -n gribby -c conda-forge python-eccodes193,200c197,206
< latMinCP = 11.7 # Min lat of CALPUFF grid
< latMaxCP = 12.2 # Max lat of CALPUFF grid
< lonMinCP = 273.2 # Min lon of CALPUFF grid
< lonMaxCP = 274.1 # Max lon of CALPUFF grid
< inDir = '../NAM_data/raw/'+date # Directory containing GRIB files
< nfiles = 17 #Number of GRIB files (files are 3 hourly, so 48 hours is 17 files including hours 0 and 48)
< outFile = '../NAM_data/processed/met_'+date+'.dat' #Output file path
< levsIncl = [1000,950,925,900,850,800,700,600,500,400,300,250,200,150,100,75,50,30,20,10,7,5,2] #pressure levels to include in output
---
> latMinCP = 21.4 # Min lat of CALPUFF grid
> latMaxCP = 25.7 # Max lat of CALPUFF grid
> lonMinCP = 119.4 # Min lon of CALPUFF grid
> lonMaxCP = 122.4 # Max lon of CALPUFF grid
> inDir = '/home/kuang/MyPrograms/UNRESPForecastingSystem/CWB_data/raw/'+date # Directory containing GRIB files
> nfiles = 15 #Number of GRIB files (files are 6 hourly, so 84 hours is 15 files including hours 0 and 48)
> outFile ='/home/kuang/MyPrograms/UNRESPForecastingSystem/CWB_data/processed/met_'+date+'.dat' #Output file path
> if os.path.isfile(outFile):
> sys.exit('exist outFile: '+outFile)
> levsIncl = [1000,925,850,700,500,400,300,250,200,150,100] #pressure levels to include in output
< lats = gribapi.grib_get_array(gidPRMSL,'distinctLatitudes')
< lons = gribapi.grib_get_array(gidPRMSL,'distinctLongitudes')
< Ni = gribapi.grib_get(gidPRMSL,'Ni')
< Nj = gribapi.grib_get(gidPRMSL,'Nj')
---
> ishp=2
> CSV='/home/kuang/MyPrograms/UNRESPForecastingSystem/data/M-A0064nc2.csv'
> df=read_csv(CSV)
> #lats = gribapi.grib_get_array(gidPRMSL,'distinctygrid')#Latitudes')
> #lons = gribapi.grib_get_array(gidPRMSL,'distinctLongitudes')
> lats = np.array(list(df.gridlat_0))
> lons = np.array(list(df.gridlon_0))
> Ni = 1158
> Nj = 673
< for i in range(len(lats)-1):
< if lats[i+1] >= latMinCP:
< iLatMinGRIB=i
< break
< for i in range(len(lats)-1):
< if lats[i+1] > latMaxCP:
< iLatMaxGRIB=i+1
< break
< for i in range(len(lons)-1):
< if lons[i+1] >= lonMinCP:
< iLonMinGRIB=i
< break
< for i in range(len(lons)-1):
< if lons[i+1] > lonMaxCP:
< iLonMaxGRIB=i+1
< break
---
> XY=np.array([twd97.fromwgs84(i,j) for i,j in zip(lats,lons)],dtype=int)
> twd97X,twd97Y=(XY[:,i] for i in [0,1])
> XminCP,YminCP=(int(i) for i in twd97.fromwgs84(latMinCP,lonMinCP))
> XmaxCP,YmaxCP=(int(i) for i in twd97.fromwgs84(latMaxCP,lonMaxCP))
> DIST =(twd97X-XminCP)**2+(twd97Y-YminCP)**2
> minD=min(DIST)
> ji=list(DIST).index(minD)
> ijLLMinGRIB = ji
> iLatMinGRIB = int(ji/Ni)
> iLonMinGRIB = ji - Ni * iLatMinGRIB
> DIST =(twd97X-XmaxCP)**2+(twd97Y-YmaxCP)**2
> minD=min(DIST)
> ji=list(DIST).index(minD)
> ijLLMaxGRIB = ji
> iLatMaxGRIB = int(ji/Ni)
> iLonMaxGRIB = ji - Ni * iLatMaxGRIB
78,81c80,83
< RXMIN = lons[iLonMinGRIB] # W-most E longitude
< RXMAX = lons[iLonMaxGRIB] # E-most E longitude
< RYMIN = lats[iLatMinGRIB] # S-most N latitude
< RYMAX = lats[iLatMaxGRIB] # N-most N latitude
---
> RXMIN = lons[ijLLMinGRIB] # W-most E longitude
> RXMAX = lons[ijLLMaxGRIB] # E-most E longitude
> RYMIN = lats[ijLLMinGRIB] # S-most N latitude
> RYMAX = lats[ijLLMaxGRIB] # N-most N latitude
97c99
< XLATDOT = lats[iLatMinGRIB+j] # N latitude of grid point
---
> XLATDOT = lats[ijLLMinGRIB+j*Ni] # N latitude of grid point
100c102
< XLONGDOT = lons[iLonMinGRIB+i] # E longitude of grid point
---
> XLONGDOT = lons[ijLLMinGRIB+i] # E longitude of grid point
< filePrefix = 'nam.t00z.afwaca'
< fileSuffix = '.tm00.grib2'
---
> filePrefix = 'M-A0064-0'
> fileSuffix = '.grb2'
209c215
< filenames.append(filePrefix+'{:02d}'.format(i*3)+fileSuffix)
---
> filenames.append(filePrefix+'{:02d}'.format((i)*6)+fileSuffix)
...
import pygrib
...
filenames = [filePrefix+'18'+fileSuffix]
filePaths = [os.path.join(oldDir,filenames[0])]
...
# intialize rain by kuang
Ni = 1158
Nj = 673
fname=filePaths[0].replace('018','012')
rain_old=pygrib.open(filePaths[0])[62].values-pygrib.open(fname)[62].values
RNgrd = np.zeros(shape=(Nj, Ni)) #by kuang
for t in range(nfiles):
...
#by kuang
if t==0:
RNgrd[:]=rain_old[:]
else:
RNgrd[:]+=pygrib.open(filePaths[t])[62].values[:]
...
for j in range(NY):
JX = j+1 # J-index of grid cell
for i in range(NX):
IX = i+1 # i-index of grid cell
...
RAIN=int(RNgrd[iLatMinGRIB+j,iLonMinGRIB+i]) /10. #mm to cm
fout.write(('{:4d}'+'{:02d}'*3+'{:3d}'*2+'{:7.1f}{:5.2f}{:2d}'+'{:8.1f}'*2+'\n').format(MYR,MMO,MDAY,MHR,IX,JX,PRES,RAIN,SC,RADSW,RADLW))
| 項目 | 發電量即時資訊 | CEMS數據 | 選擇考量 |
|---|---|---|---|
| 污染源完整性 | 所有發電機組都有 | 有的污染源沒有(可能因為規模太小未列管) | 沒有數據就無從推估 |
| 污染項目 | 無(只有運轉率%) | 煙氣流量、溫度、SOX、NOX及不透光率 | 後者太過複雜還有待QCQA |
| 時間頻率 | 10min | 15min | 此處作業只需要逐時 |
| 資料穩定性 | 穩定 | 不甚穩定(CEMS允許一定時數之離線) |
CP_NO,C_NO,All_In_One,CO_GPS,DIA,EnergyForm,HEI,NMHC_GPS,NOX_GPS,NO_S,PM25_GPS,PM_GPS,PlantName,
SOX_GPS,SUM_EMI,TEMP,UnitName,VEL,UTM_N,UTM_E管編+煙道(**管煙**)、管編、集合否(T/F)、CO、DIA、發電形態、煙囪高、NMHC、NOX、管道編號、PM25、PM、廠名、SOX、總量、溫度、機組名稱、流速、座標
$ head g20220412.csv
CP_NO,TEMP,VEL,CO_GPS,NMHC_GPS,NOX_GPS,PM25_GPS,PM_GPS,SOX_GPS,DateHr
F1700736P601,114,16.57785,0.0,0.0,21.48246822694089,1.1103541221509972,1.882682180377493,19.653922323336385,2022041200
F1700736P701,108,16.575588,0.0,0.0,16.499618542048232,1.1976660590277777,2.0307950954861114,12.677973511904762,2022041200
kuang@master /home/sespub/power
$ head p20220412.csv
CP_NO,HEI,DIA,TEMP,VEL,UTM_E,UTM_N
C1400170P301,202.0,6.4,127,20.0,321548.0,2777220.0
C1400170P401,205.0,6.4,118,20.0,321548.0,2777220.0
E5400878P001,60.0,6.2,150,9.1,180595.0,2503815.0
kuang@master /home/sespub/power
$ cat rd_today.py
#!/cluster/miniconda/envs/unresp/bin/python
from pandas import *
import subprocess, os
from pypinyin import pinyin, lazy_pinyin
cole=['CO_EMI', 'NMHC_EMI', 'NOX_EMI', 'PM25_EMI', 'PM_EMI', 'SOX_EMI']
colg=[i.replace('EMI','GPS') for i in cole]
hdtv=[ 'HEI', 'DIA', 'TEMP', 'VEL']
date=subprocess.check_output('date -d "-1 day" +%Y%m%d',shell=True).decode('utf8').strip('\n')
df=read_csv('names.csv')
unit2={}
col=['CP_NO']+hdtv+colg
for c in col:
dd= {i:j for i,j in zip(list(df.UnitName),list(df[c]))}
unit2.update({c:dd})
td=DataFrame({})
for t in range(24):
tt='{:02d}'.format(t)
fname='b'+date+tt+'.txt'
if not os.path.exists(fname):continue
with open(fname,'r') as f:
lines=[i.strip('\n') for i in f]
a=[i.split(',')[2] for i in lines[1:]]
b=[]
for i in a:
ll=lazy_pinyin(i)
s=''
for ii in ll:
s+=ii
b.append(s)
perc=[i.split(',')[5] for i in lines[1:] ]
dft=DataFrame({'Name':b,'Perc':perc})
dft=dft.loc[dft.Name.map(lambda x:x in set(df.UnitName))].reset_index(drop=True)
dft.Perc=[float(i[:-1])/100. for i in dft.Perc]
dft['DateHr']=[date+tt for i in range(len(dft))]
dft['CP_NO']=[unit2['CP_NO'][i] for i in dft.Name]
dft=dft.loc[dft.Perc>0]
td=td.append(dft,ignore_index=True)
for c in colg:
c0=np.array([unit2[c][i] for i in td.Name])
td[c]=c0*td.Perc
for c in hdtv:
c0=np.array([unit2[c][i] for i in td.Name])
if c=='VEL':
td[c]=c0*td.Perc
else:
td[c]=c0
td[col[:1]+col[3:]+['DateHr']].set_index('CP_NO').to_csv('g'+date+'.csv')
df=read_csv('names.csv')
df=df.loc[df.CP_NO.map(lambda x:x in set(td.CP_NO))].reset_index(drop=True)
df[col[:5]+['UTM_E','UTM_N']].set_index('CP_NO').to_csv('p'+date+'.csv')
...
echo ${rundate} ${prevdate} ${enddate}> ptem_PWR.inp
/home/cpuff/2018/ptem/ptem_PWR<ptem_PWR.inp
if ! [ -e ptemarb_pwr.dat ]; then echo -n "fail ptemarb_PWR !";exit 0;fi
NPT2=$(head -n11 ptemarb_pwr.dat|tail -n1| awk '{print $1}')
...
sed -e ...
-e "s/?NPT2?/$NPT2/g" \
./CALPUFF_INP/calpuff_template.inp > ./CALPUFF_INP/calpuff.inp
read(cfver,'(f)') cfdataset(unresp)
kuang@master /cluster/CALPUFF6/CALMET
$ diff CALMET_v6.5.0_L150223/CALMET.FOR new/calmet.for
21043c21045
< save it1,it2,isec1,isec2
---
> save it1,it2,isec1,isec2,nlevag11
kuang@master ~/MyPrograms/UNRESPForecastingSystem/CALPUFF_MODS/CALMET
$ diff calmet.for /cluster/CALPUFF6/CALMET/new/calmet.for
21045c21045
< save it1,it2,isec1,isec2,nlevag1,msec
---
> save it1,it2,isec1,isec2,nlevag11
read(cfver,'(f)') cfdatasetLatitude_Pole, Longitude_Pole = 23.61000, 120.9900
Xcent, Ycent = twd97.fromwgs84(Latitude_Pole, Longitude_Pole)
$ diff CALPUFF_v7.2.1_L150618/output.f new/output.f
125a126
> character*150 infile
373a375,392
> c--------------------------------------------------------------
> ckuang
> c --- Sara 08/10/2004: Just to have an output of the concentration
> c --- directly readable.
> c --- Concentration in g/m^3 is written for each receptor
> do ispec=1,nspec
> write(infile,10000) ispec, istep
> c & I2.2,I4.4,'.vtk')
> 10000 format('concrec',I2.2,I4.4,'.dat')
> c print *, nspec,',',ispec,',',nn,',',infile,',',
> c + nrec,',',chirec(1,ispec)
> open(50,file=infile)
> do i=1,nrec
> write(50,*) chirec(i,ispec)
> end do
> close(50)
> end do
kuang@master ~/MyPrograms/UNRESPForecastingSystem/Python
$ cat conc2pm25.py
import numpy as np
import os,sys,subprocess
from pandas import *
CSPEC='SO2 SO4 NOX HNO3 NO3 PMS1 PMS2 PMS3'.split()
fnames=list(subprocess.check_output('ls concrec*dat',shell=True).split(b'\n'))
fnames=[i.decode('utf8') for i in fnames if len(i)>0 ]
if len(fnames)==0:sys.exit('concrec not found')
wc=int(subprocess.check_output('cat '+fnames[0]+'|wc -l',shell=True).split(b'\n')[0])
jt=[int(fname.split('/')[-1].replace('.dat','')[-4:]) for fname in fnames if len(fname)>0]
js=[int(fname.split('/')[-1].replace('.dat','')[-6:-4]) for fname in fnames if len(fname)>0]
df=DataFrame({'hr':jt,'spec':js,'fname':fnames})
df=df.loc[df.spec>0].reset_index(drop=True)
C=np.zeros(shape=(max(js)+1,max(jt)+1,wc))
for i in range(len(df)):
with open(df.loc[i,'fname'],'r') as f:
tmp=[float(l.strip('\n')) for l in f]
C[df.loc[i,'spec'],df.loc[i,'hr'],:]=tmp[:]
so4 =C[1,:,:]
hno3=C[3,:,:]
no3 =C[4,:,:]
p25 =C[5,:,:]
nh4=so4*(36./96.)+no3*(18./62.)+hno3*(18./63.)
total=so4+no3+hno3+nh4+p25
fnRoot=fnames[0].replace('.dat','')[:-6]+'00'
for it in range(1,max(jt)+1):
fname=fnRoot+'{:04d}'.format(it)+'.dat'
with open(fname,'w') as f:
for ic in range(wc):
f.write(str(total[it,ic])+'\n')
100c109
< nconc = 48
---
> nconc = 83
32d31
< import utm
35a35,36
> import twd97
123,128c128,131
> Latitude_Pole, Longitude_Pole = 23.61000, 120.9900
> Xcent, Ycent = twd97.fromwgs84(Latitude_Pole, Longitude_Pole)
> ll = np.array([twd97.towgs84(i*1000+Xcent,j*1000+Ycent) for i, j in zip(x, y)])
> lat, lon = (ll[:, i] for i in [0, 1])
166,169c169,172
> Latitude_Pole, Longitude_Pole = 23.61000, 120.9900
> Xcent, Ycent = twd97.fromwgs84(Latitude_Pole, Longitude_Pole)
> ll = np.array([twd97.towgs84(i*1000+Xcent,j*1000+Ycent) for i, j in zip(x2, y2)])
> lat, lon = (ll[:, i] for i in [0, 1])
273,274c276,277
< self.binLims = [10, 350, 600, 2600, 9000, 14000] # SO2 bin limits
< self.binLimsSO4 = [1E-8, 12, 35, 55, 150, 250] # SO4 bin limits from:
---
> self.binLims = [1E-3,1E-2,1,5, 10, 350] # SO2 bin limits
> self.binLimsSO4 = [1E-3, 1E-2,1E-1, 1, 5, 15] # SO4 bin limits from:
399,400c403,404
> latTicks = np.arange(round(latMin, 1), round(latMax, 1) + 0.1, 0.5)
> lonTicks = np.arange(round(lonMin, 1), round(lonMax, 1) + 0.1, 0.5)
cd /home/cpuff/UNRESPForecastingSystem/VIZ_SITE_CODE/public_html
/cluster/anaconda3/bin/python3.7/python -m http.server --bind 200.200.12.191 8030 >&/dev/null &
...
for i in $(ls ${s}${static}??.PNG);do
/usr/bin/convert ${i} -bordercolor white -trim tmp.png
/usr/bin/convert tmp.png -bordercolor white -border 5%x5% ${i}
done
/usr/bin/convert -dispose 2 -coalesce +repage -background none ${s}${static}??.PNG ${s}.gif
cp ${s}.gif /var/www/html/LC-GIF-Player/example_gifs
...