Py学习  »  Python

利用Python将WRFOUT等nc文件处理成系统可视化的json文件,内容包括500mb等高线、风羽集合、等压线集合、台风路径

气象学家 • 2 年前 • 737 次点击  

  第一时间获取气象科研资讯

气象学家公众号交流群

加入



      去年一月份,根据某项目的要求,我们需要从预报的WRFOUT文件中提取500mb等压线、风羽集合、等压线集合以及台风路径,并以json文件格式呈现。这种json文件可以直接在网页端显示,或者在我们自行创建的系统中展示。它具有较高的地理匹配度,与以往使用Python绘制图片的方法有明显不同。我将提供处理500mb等压线等内容的代码注解,所有代码都将通过百度网盘分享。

       首先是导入各种python库:

import xarray as xrimport numpy as npimport mathimport netCDF4 as ncfrom netCDF4 import Dataset,num2date,date2numfrom wrf import getvar,ll_to_xy,interplevel,vinterp,get_cartopy,latlon_coords,cartopy_xlim,cartopy_ylimfrom math import atan2,piimport pandas as pdimport datetimeimport wrf import geopandas as gpdimport salemfrom salem.utils import get_demo_filefrom matplotlib import colorsimport matplotlib.pyplot as pltimport matplotlib as mplfrom datetime import datetime,timedeltafrom cartopy.io.shapereader import Readerimport cartopy.crs as crsimport matplotlib.ticker as mtickerfrom salem.utils import get_demo_filefrom scipy.interpolate import griddatafrom salem import cache_dir, sample_data_dirimport json

       其次通过读取WRFOUT数据提取500mb等高线、风羽集合、等压线集合的数据:

Ht_500mb=[]Wspd10=[]Wdir10=[]Slp=[]timerange=pd.date_range(start='2019-08-05 18:00:00',end='2019-08-11 12:00:00',freq='H')for t in range(0,len(timerange),6):    print(str(timerange[t]))    t1=str(timerange[t]).replace(' ','_').replace(':','_')    ncfile = Dataset("./wrfout_d02_2019-08-05_18:00:00")    lat=getvar(ncfile, "XLAT",timeidx=0).data    lon=getvar(ncfile, "XLONG",timeidx=0).data    time=getvar(ncfile,"times",timeidx=t).data    z = getvar(ncfile, "z",timeidx=t)    p = getvar(ncfile, "pressure",timeidx=t)    # Compute the 500 MB Geopotential Height    ht_500mb = (interplevel(z, p, 500.)/10).data    slp=getvar(ncfile,"slp",timeidx=t).data    wspd10=getvar(ncfile,"wspd10",timeidx=t).data    wdir10=getvar(ncfile,"wdir10",timeidx=t).data    llat1=np.arange(1.51,40.34,0.03)
llon1=np.arange(108.00,130.93,0.03) llon,llat=np.meshgrid(llon1,llat1)
llat2=np.arange(1.51,40.34,1) llon2=np.arange(108.00,130.93,1) llon_wind,llat_wind=np.meshgrid(llon2,llat2) #插值数据到指定的经纬度 ht_500mb_grid=griddata((lat.ravel(),lon.ravel()),ht_500mb.ravel(),(llat,llon), method='linear') wspd10_grid=griddata((lat.ravel(),lon.ravel()),wspd10.ravel(),(llat_wind,llon_wind), method='linear') wdir10_grid=griddata((lat.ravel(),lon.ravel()),wdir10.ravel(),(llat_wind,llon_wind), method='linear') slp_grid=griddata((lat.ravel(),lon.ravel()),slp.ravel(),(llat,llon), method='linear') Ht_500mb.append(ht_500mb_grid[np.newaxis,:]) Wspd10.append(wspd10_grid[np.newaxis,:]) Wdir10.append(wdir10_grid[np.newaxis,:]) Slp.append(slp_grid[np.newaxis,:])Ht_500mb=np.concatenate(Ht_500mb, axis=0)Wspd10=np.concatenate(Wspd10, axis=0)Wdir10=np.concatenate(Wdir10, axis=0)Slp=np.concatenate(Slp, axis=0)
timerange=pd.date_range(start='2019-08-05 18:00:00',end='2019-08-11 12:00:00',freq='6H')ht500mb=xr.Dataset()ht500mb['time']=(['time'],timerange)ht500mb['latitude']=(['latitude'],llat1)ht500mb['longitude']=(['longitude'],llon1)ht500mb['ht_500mb']=(['time','latitude','longitude'],Ht_500mb)ht500mb.to_netcdf('./ht_500mb_d02.nc')  spd=xr.Dataset()spd['time']=(['time'],timerange)spd['latitude']=(['latitude'],llat2)spd['longitude']=(['longitude'],llon2)spd['SPD10']=(['time','latitude','longitude'],Wspd10)spd['DIR10']=(['time','latitude','longitude'],Wdir10)spd.to_netcdf('./spd_d02.nc') ps=xr.Dataset()ps['time']=(['time'],timerange)ps['latitude']=(['latitude'],llat1)ps['longitude']=(['longitude'],llon1)ps['PS']=(['time','latitude','longitude'],Slp)ps.to_netcdf('./slp_d02.nc')

      利用salem掩膜陆地数据(可选可不选,看个人需求并输出风羽json文件

#风jsonspd_cut=xr.open_dataset('./spd_d02.nc')#2022.1.17add#spd_cut=spd.salem.roi(shape=shp)#spd_cut.to_netcdf('./spd_d02_cut.nc') #spd_cut=xr.open_dataset('./spd_d02_cut.nc') spd10_cut=spd_cut['SPD10'].datadir10_cut=spd_cut['DIR10'].datalon=spd_cut['longitude'].datalat=spd_cut['latitude'].datalon,lat=np.meshgrid(lon,lat)for j in range(len(timerange)):  data=pd.DataFrame()  data['SPD10']=spd10_cut[j,:,:].ravel()  data['DIR10']=dir10_cut[j,:,:].ravel()  data['lat']=lat.ravel()  data['lon']=lon.ravel()  data1=data.dropna(axis=0,how='any').values  windjson=[]  for  i in range(data1.shape[0]):    paths = {}    paths['coordinates']=[data1[i,3],data1[i,2]]    paths['angle']=data1[i,1]    paths['power']=data1[i,0]    windjson.append(paths)  buffer={}  buffer["currentDirection"]=[windjson]  filename='./json/2022_风{:}.json'.format(str(timerange[j]).replace(' ','_').replace(':','_'))  with open(filename,'w') as file_obj:    json.dump(buffer,file_obj, indent=4, separators=(',', ': '))

      利用salem掩膜陆地数据(可选可不选,看个人需求)并输出500mb等高线和等压线集合json文件,在这一步之前,需要介绍从等压线绘图中提取等压线数据的函数文件:

def get_contour_slp(c):   contours = []   idx = 0   for cc,vl in zip(c.collections,c.levels):      # print(cc.get_paths())      # for each separate section of the contour line      for pp in cc.get_paths():          # print(pp)          paths = {}          paths["type"]="Feature"
paths["properties"]={"value":vl} # vl 是属性值 xy = [] # for each segment of that section for vv in pp.iter_segments(): xy.append([np.round(vv[0][0],5),np.round(vv[0][1],5)]) #vv[0] 是等值线上一个点的坐标,是 1 个 形如 array[12.0,13.5] 的 ndarray。 if xy[0]==xy[-1]: paths["geometry"]={"type":'Polygon','coordinates':[]} paths["geometry"]["coordinates"]=[xy] else: paths["geometry"]={"type":'LineString','coordinates':[]} paths["geometry"]["coordinates"]=xy contours.append(paths) idx +=1 return contours#############################def get_contour_ht500mb(c): contours = [] idx = 0 # for each contour line #print(cn.levels) for cc,vl in zip(c.collections,c.levels): # print(cc.get_paths()) # for each separate section of the contour line for pp in cc.get_paths(): # print(pp) paths = {} paths["type"]="Feature"
paths["properties"]={"value":vl} # vl 是属性值 xy = [] # for each segment of that section for vv in pp.iter_segments(): xy.append([float(vv[0][0]),float(vv[0][1])]) #vv[0] 是等值线上一个点的坐标,是 1 个 形如 array[12.0,13.5] 的 ndarray。 if xy[0]==xy[-1]: paths["geometry"]={"type":'Polygon','coordinates':[]} paths["geometry"]["coordinates"]=[xy] else: paths["geometry"]={"type":'LineString','coordinates':[]} paths["geometry"]["coordinates"]=xy contours.append(paths) idx +=1 return contours

      最后提取500mb等高线和等压线集合json文件代码:

shp = gpd.read_file('./ne_10m_ocean_scale_rank/ne_10m_ocean_scale_rank.shp')ht500mb1=xr.open_dataset('./ht_500mb_d02.nc')#2022.1.17add#ht500mb_cut=ht500mb.salem.roi(shape=shp)#ht500mb_cut.to_netcdf('./ht_500mb_d02_cut.nc') #ht500mb_cut=xr.open_dataset('./ht_500mb_d02_cut.nc') #ht500mb=ht500mb_cut['ht_500mb'].data#lon=ht500mb_cut['longitude'].data#lat=ht500mb_cut['latitude'].data
#2022.1.17addht500mb=ht500mb1['ht_500mb'].datalon=ht500mb1['longitude'].datalat=ht500mb1['latitude'].data
ps1=xr.open_dataset('./slp_d02.nc')#2022.1.17add#ps_cut=ps.salem.roi(shape=shp)#ps_cut.to_netcdf('./slp_d02_cut.nc') #ps_cut=xr.open_dataset('./slp_d02_cut.nc') #slp=ps_cut['PS'].data
#2022.1.17addslp=ps1['PS'].data# contour 函数是绘制等值线的方法,它将根据输入的矩阵自动计算等值线的位置for i in range(len(timerange)): fig = plt.figure(figsize=(8, 10)) ax = fig.add_subplot(projection=crs.PlateCarree()) ocean=Reader(r'./ne_10m_land_scale_rank/ne_10m_land_scale_rank.shp').geometries() ax.add_geometries(ocean,crs.PlateCarree(),facecolor='none',edgecolor='k',linewidth=0.5) c_ht500mb = ax.contour(lon, # x轴坐标 lat, # y轴坐标 ht500mb[i,:,:], # 选取550hpa位置的位势高度数据
# slp[-1,:,:], levels=16, # 等值线数量 colors='blue', linewidths=1) # plt.show() def get_contour_ht500mb(c): contours = [] idx = 0 # for each contour line #print(cn.levels) for cc,vl in zip(c.collections,c.levels): # print(cc.get_paths()) # for each separate section of the contour line for pp in cc.get_paths(): # print(pp) paths = {} paths["type"]="Feature"
paths["properties"]={"value":vl} # vl 是属性值 xy = [] # for each segment of that section for vv in pp.iter_segments(): xy.append([float(vv[0][0]),float(vv[0][1])]) #vv[0] 是等值线上一个点的坐标,是 1 个 形如 array[12.0,13.5] 的 ndarray。 if xy[0]==xy[-1]: paths["geometry"]={"type":'Polygon','coordinates':[]} paths["geometry"]["coordinates"]=[xy] else: paths["geometry"]={"type":'LineString','coordinates':[]} paths["geometry"]["coordinates"]=xy contours.append(paths) idx +=1 return contours buffer={} buffer["type"]="FeatureCollection" buffer["features"]=get_contour_ht500mb(c_ht500mb) filename='./json/2022_500毫巴{:}.json'.format(str(timerange[i]).replace(' ','_').replace(':','_')) with open(filename,'w') as file_obj: json.dump(buffer,file_obj, indent=4, separators=(',', ': ')) fig = plt.figure(figsize=(8, 10)) ax = fig.add_subplot(projection=crs.PlateCarree()) ocean=Reader(r'./ne_10m_land_scale_rank/ne_10m_land_scale_rank.shp').geometries() ax.add_geometries(ocean,crs.PlateCarree(),facecolor='none',edgecolor='k',linewidth=0.5) c_slp = ax.contour(lon, # x轴坐标 lat, # y轴坐标 # ht500mb[-1,:,:], # 选取550hpa位置的位势高度数据 slp[i,:,:], levels=16, # 等值线数量 colors='blue', linewidths=1) def get_contour_slp(c): contours = [] idx = 0 for cc,vl in zip(c.collections,c.levels): # print(cc.get_paths()) # for each separate section of the contour line for pp in cc.get_paths(): # print(pp) paths = {} paths["type"]="Feature"
paths["properties"]={"value":vl} # vl 是属性值 xy = [] # for each segment of that section for vv in pp.iter_segments(): xy.append([np.round(vv[0][0],5),np.round(vv[0][1],5)]) #vv[0] 是等值线上一个点的坐标,是 1 个 形如 array[12.0,13.5] 的 ndarray。 if xy[0]==xy[-1]: paths["geometry"]={"type":'Polygon','coordinates':[]} paths["geometry"]["coordinates"]=[xy] else: paths["geometry"]={"type":'LineString','coordinates':[]} paths["geometry"]["coordinates"]=xy contours.append(paths) idx +=1 return contours buffer={} buffer["type"]="FeatureCollection" buffer["features"]=get_contour_slp(c_slp) filename='./json/2022_气压{:}.json'.format(str(timerange[i]).replace(' ','_').replace(':','_')) with open(filename,'w') as file_obj: json.dump(buffer,file_obj, indent=4, separators=(',', ': ')) #高低气压坐标以及数值.json high_value=[] high_points=[] low_value=[] low_points=[] highlow={'low':{},'high':{}} for path in get_contour_slp(c_slp): #print(path) if path['properties']['value'] in c_slp.levels[-2:]: if path['geometry']['coordinates'][0] == path['geometry']['coordinates'][-1]: print(path) high_value.append(path['properties']['value']) high_points.append([np.mean(np.array(path['geometry']['coordinates']).reshape(-1,2)[:,0]),np.mean(np.array(path['geometry']['coordinates']).reshape(-1,2)[:,1])]) if path['properties']['value'] in c_slp.levels[:2]: if path['geometry']['coordinates'][0] == path['geometry']['coordinates'][-1]: print(path) low_value.append(path['properties']['value']) low_points.append([np.mean(np.array(path['geometry']['coordinates']).reshape(-1,2)[:,0]),np.mean(np.array(path['geometry']['coordinates']).reshape(-1,2)[:,1])]) highlow['high']={'value':high_value,'points':high_points} highlow['low']={'value':low_value,'points':low_points} filename='./json/2022_高低气压坐标以及数值{:}.json'.format(str(timerange[i]).replace(' ','_').replace(':','_')) with open(filename,'w') as file_obj: json.dump(highlow,file_obj, indent=4, separators=(',', ': '))

      在这个http://geojson.io/网站上我们可以将500mb等高线和等压线的json文件导入,来看到我们的效果图:

      其他的json文件目前还不支持在该网站的显示,需要系统自行适配。台风路径的json文件绘制代码过于冗长,其原理类似,即将最低气压坐标点放入json的字典中(后续将单独讲解一下),并根据10米最大风速规则添加上台风属于'热带低压'、'热带风暴'、'强热带风暴'、'台风'、'强台风'或'超强台风'中的哪一种?另外,还绘制了水汽通量图和台风路径图来验证提取的数据是否准确?







声明:欢迎转载、转发本号原创内容,可留言区留言或者后台联系小编(微信:gavin7675)进行授权。气象学家公众号转载信息旨在传播交流,其内容由作者负责,不代表本号观点。文中部分图片来源于网络,如涉及作品内容、版权和其他问题,请后台联系小编处理。


往期推荐

 获取ERA5-Land陆面高分辨率再分析数据(32TB)

 1942-2022年中国地面气象站观测数据免费分享

 获取全球GPM降水数据,半小时/逐日(4TB)

  获取1998-2019 TRMM 3B42逐日降水数据

★ 获取最新版本CMIP6降尺度数据集30TB

★ 获取ERA5常用变量再分析数据26TB

 EC数据商店推出Python在线处理工具箱

★ EC打造实用气象Python工具Metview

★ 机器学习简介及在短临天气预警中的应用

★ Nature-地球系统科学领域的深度学习及理解

★ 采用神经网络与深度学习来预报降水、温度

★ 灵魂拷问:ChatGPT对气象人的饭碗是福是祸?

★  气象局是做啥的?气象局的薪水多少?

★ 一位气象学家尝试ChatGPT复现Nature子刊的研究,他真的会面临失业吗?!


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/163192