micaps(Meteorology Information Comprehensive Analysis Process System)是气象上业务分析常用的软件。
大体这样的一个界面。借助这样的软件,可以综合系统的进行地面、高空、卫星等等各种气象数据的查看。
micaps下载链接
micaps4帮助文档
凡是记事本能打开的数据,我们都能用python来进行读取。
下面将介绍几种常用的micaps气象数据的python读取方法
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
import os
from mpl_toolkits.basemap import Basemap
import numpy.ma as ma
f=open(filename,mode='r')
#此时每行的都为 字符格式
str_data = f.readlines()
data = []
for i in range(len(str_data)):
#将每行的字符类型的数字转为float型,如果某一行存在非数字字符,则跳过该行
try:
#依次读取每一行数据
line_data = str_data[i]
#如果该行为空(只有换行符),则跳过;否则,将其转换为数值型
if len(line_data)>1:
#去除换行符并将每个字符数据分开
line_data = line_data.strip().split()
#将数据类型由str转换为float型
line_data=[float(line_data[i]) for i in range(len(line_data))]
#将每一行变成 (1,n)的array数组
line_data = np.array(line_data).reshape(1,-1)
data.append(line_data)
except Exception as e:
print('第{:}行含有非数字字符 '.format(i))
print(e)
print()
pass
我们选取一个 micaps/surface/plot/18080405.000 文件为例:
可以看到,第一个str_data 每一行和记事本打开的一样,但都是 str即字符 类型。
变成 data后,每一行依然内容不变,但是格式变成了float 的数组。点开第二行,数值和txt的第三行完全一致;-
变成数组之后,就可以很方便的进行后续操作。eg: 由数据说明可知:每一个站点 是一个长度为26的数组,也就是说 data列表中,data[1] + data[2] 才是一个站点的 完整观测信息;
后续大多micaps类型的数据读取,都需要先进行这样的操作。
def get_station_data(filename):
'''
func: 获取站点观测数据
inputs:
filename: 文件名
file_type : 'plot'
surface/plot类型的数据存储方式为:
第0行:数据的时间信息
之后,每一个站点的信息占据两行: 站台号 经纬度 观测要素 等信息;
return:
station_data 每一行都是一个站点的信息
'''
f=open(filename,mode='r')
#此时每行的都为 字符格式
str_data = f.readlines()
data = []
for i in range(len(str_data)):
#将每行的字符类型的数字转为float型,如果某一行存在非数字字符,则跳过该行
try:
#依次读取每一行数据
line_data = str_data[i]
#如果该行为空(只有换行符),则跳过;否则,将其转换为数值型
if len(line_data)>1:
#去除换行符并将每个字符数据分开
line_data = line_data.strip().split()
#将数据类型由str转换为float型
line_data=[float(line_data[i]) for i in range(len(line_data))]
# line_data = np.array(line_data).reshape(1,-1)
data.append(line_data)
except Exception as e:
print('第{:}行含有非数字字符 '.format(i))
print(e)
print()
pass
#第一行表示时间信息
# T = data[0]
station_data = []
for i in range(len(data[1::2])):
station_data.append(data[i*2+1]+data[i*2+2])
station_data = [np.array(station_data[i]).reshape(1,-1) for i in range(len(station_data))]
station_data = np.concatenate(station_data,axis=0)
return station_data
还是以 filename = '/micaps/surface/plot/18080405.000’为例:
即每一行就是一个站点的完备观测信息:
站点数据绘制
def scatter_station_on_map(station_lon,station_lat,station_value, fill_value=9999,
loc_range = [18,54,73,135],det = 5):
'''
func: 将站点位置,根据站点要素值大小,scatter到地图底图上
inputs:
station_lon: 站点的经度
station_lat: 站点的纬度
station_value: 对应站点的要素值
上述三个数据类型 可以是 数组,也可以是 列表
fill_value : 缺测填充值.默认为 9999;
loc_range: scatter区域范围。默认范围为中国大陆区域
[18,54,73,135] = [lat_min,lat_max,lon_min,lon_max]
'''
#step1: 先将station数据转换一下格式
station_lon = np.array(station_lon).reshape(-1,1).ravel()
station_lat = np.array(station_lat).reshape(-1,1).ravel()
#使用mask数组;将value=fill_value的值跳过;
#mask数组的好处在于:在后续数值运算或者画图中,会自动跳过fille_value值
station_value = np.array(station_value).reshape(-1,1).ravel()
mask = station_value == fill_value
station_value = ma.array(station_value,mask=mask)
lat_min = loc_range[0]
lat_max = loc_range[1]
lon_min = loc_range[2]
lon_max = loc_range[3]
fig = plt.figure(figsize=(14,8))
ax1 = fig.add_axes([0.1,0.1,0.8,0.8])
m=Basemap(projection='cyl',llcrnrlat=lat_min,llcrnrlon=lon_min,
urcrnrlat=lat_max,urcrnrlon=lon_max,resolution='l',ax=ax1)
#读取省界数据。下载省界数据链接为:https://blog.csdn.net/weixin_36677127/article/details/83314583
m.readshapefile('D:/geo_data/gadm36_CHN_shp/gadm36_CHN_1','states',
drawbounds=True)
h = m.scatter(station_lon,station_lat,
s = station_value, #marker大小与数值相关
c = station_value, #颜色深浅与数值大小相关
cmap = plt.cm.rainbow,
)
cb = m.colorbar(h,size = '4%')
cb.set_label('',fontsize = 20)
cb.ax.tick_params(labelsize=16)
x_grid = np.arange(lon_min,lon_max+1,det)
y_grid = np.arange(lat_min,lat_max+1,det)
m.drawparallels(x_grid)
m.drawmeridians(y_grid)
plt.grid()
plt.xticks(x_grid,x_grid,fontsize = 16)
plt.yticks(y_grid,y_grid,fontsize = 16)
plt.xlabel('longitude: °E',fontsize = 16)
plt.ylabel('latitude: °N',fontsize = 16)
# plt.title(u'中国站点分布',fontsize = 20)
#画出海岸线和国境线
m.drawcoastlines()
m.drawcounties()
m.drawcountries()
return None
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
import os
from mpl_toolkits.basemap import Basemap
import numpy.ma as ma
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
#有中文出现的情况,需要u'内容'
f2 = 'surface/plot/18080405.000'
plot_p = get_station_data(f2)
scatter_station_on_map(station_lon = plot_p[:,1],station_lat = plot_p[:,2],
station_value = plot_p[:,19]) #plot_p[:,19]为地面温度
在这里插入图片描述
由这段代码可知,温度越高,颜色越深,小圆圈越大。从这张图可以大概看出中国测站的分布密集程度,与经济和人口是息息相关的 。另外,还可以看出,黑龙江、西藏、兰州、云南都是避暑胜地啊!
def get_physic_data(filename,plot = True,label_gap = 10):
'''
doc: physic类型的数据,只有08时和20时有;空间分辨率为 2*2°
fun: 获取micaps/physic的数据,转换为矩阵形式
physic类型的数据存储形式为:
第0行为时间信息
第1行为 经纬度网格信息
第2行为 网格shape
从第2行开始,现实中的每一行数据(纬向方向)由 6行组成;
#eg: shape为 29*53,每一行数据有53个数据;而在存储时,每10个数据占据一行,
# 剩下3个数据独占一行,因此 6行对应1行;
input:
file: 文件名
plot: 默认True,数据可视化
label_gap: Plot中,x和y label的坐标经纬度间隔;默认为10
return:
lon_grid : 经度网格信息
lat_grid : 纬度网格信息
rain_grid:数值
以list形式返回
[lon_grid,lat_grid,physic_data]
'''
f=open(filename,mode='r')
#此时每行的都为 字符格式
str_data = f.readlines()
data = []
for i in range(len(str_data)):
#将每行的字符类型的数字转为float型,如果某一行存在非数字字符,则跳过该行
try:
#依次读取每一行数据
line_data = str_data[i]
#如果该行为空(只有换行符),则跳过;否则,将其转换为数值型
if len(line_data)>1:
#去除换行符并将每个字符数据分开
line_data = line_data.strip().split()
#将数据类型由str转换为float型
line_data=[float(line_data[i]) for i in range(len(line_data))]
# line_data = np.array(line_data).reshape(1,-1)
data.append(line_data)
except Exception as e:
print('第{:}行含有非数字字符 '.format(i))
print(e)
print()
pass
#第0行为时间信息
#第1行为 经纬度网格信息
# T = data[0]
loc_info = data[1]
det_lat = abs(loc_info[0])
det_lon = abs(loc_info[1])
lon_max = loc_info[3]
lon_min = loc_info[2]
lat_max = loc_info[4]
lat_min = loc_info[5]
lat_range = np.arange(lat_min,lat_max+det_lat,det_lat)
lon_range = np.arange(lon_min,lon_max+det_lon,det_lon)
lon_grid,lat_grid = np.meshgrid(lon_range,lat_range)
#使得高纬度在上(0行为最高纬度)
lat_grid = lat_grid[-1::-1,:]
#获取每行的最大数据序列长度,一般为 10
length = len(data[3])
#一个完整序列,对应多少行,这里为6
k = int(np.ceil(len(lon_range)/length))
physic_data = []
for i in range(len(lat_range)):
a = []
for j in range(k):
a = a+data[i*k+3+j]
a = np.array(a).reshape(1,-1)
physic_data.append(a)
physic_data = np.concatenate(physic_data,axis = 0)
if plot:
plt.figure(figsize = (12,8))
cs = plt.contourf(physic_data[-1::-1], #Plt.contourf默认将第一行画在最底部
extend='both') #extend参数使得colorbar两端变尖
#cs.cmap.set_over('red')
#cs.cmap.set_under('blue')
#cs.changed()
cb = plt.colorbar(cs)
cb.ax.tick_params(labelsize=16) #设置色标刻度字体大小。
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
font = {'family' : 'serif',
'color' : 'darkred',
'weight' : 'normal',
'size' : 20,
}
cb.set_label('',fontdict=font) #设置colorbar的标签字体及其大小
#确定横纵坐标轴的gap
#label_gap = label_gap
label_gap = 10
plt.xticks(np.arange(0,len(lon_range),label_gap/det_lon), np.arange(lon_min,lon_max+1,label_gap,dtype = int),fontsize = 16)
plt.yticks(np.arange(0,len(lat_range),label_gap/det_lat), np.arange(lat_min,lat_max+1,label_gap,dtype = int),fontsize = 16)
plt.xlabel("longitude°E",fontsize = 20)
plt.ylabel("latitude°N",fontsize = 20)
plt.title(filename,fontsize = 20)
return [lon_grid,lat_grid,physic_data]
数据可视化,叠加在 地图底图上
def contourf_data_on_map(data,lon_grid,lat_grid,gap = 5):
'''
func: 传入数据和对应的经纬范围,将数据叠加在地图底图上
inputs:
data: 网格数据
lon_grid : 网格经度
lat_grid :网格纬度
gap : 地图上横纵坐标显示的经纬度数值间隔。即tick_gap
return
输出一张图
return None
'''
lat_min = np.min(lat_grid)
lat_max = np.max(lat_grid)
lon_min = np.min(lon_grid)
lon_max = np.max(lon_grid)
# det_grid = lon_grid[0,1]-lon_grid[0,2]
fig = plt.figure(figsize=(14,8))
ax1 = fig.add_axes([0.1,0.1,0.8,0.8])
m=Basemap(projection='cyl',llcrnrlat=lat_min,llcrnrlon=lon_min,
urcrnrlat=lat_max,urcrnrlon=lon_max,resolution='l',ax=ax1)
m.readshapefile('D:/geo_data/gadm36_CHN_shp/gadm36_CHN_1','states',
drawbounds=True)
#若lat_grid高纬度值在最上面,则将数据做对应行数反转;
#因为使用m.contourf时,默认低纬在上
if lat_grid[0,0] == lat_max:
lat_grid = lat_grid[-1::-1]
data = data[-1::-1]
h = m.contourf(lon_grid,lat_grid,data,
# levels= np.arange(0,32+1,4),
extend='both',
cmap = plt.cm.rainbow)
cb = m.colorbar(h,size = '4%')
cb.set_label('',fontsize = 20)
cb.ax.tick_params(labelsize=16)
x_grid = np.arange(lon_min,lon_max+gap,gap,dtype = int)
y_grid = np.arange(lat_min,lat_max+gap,gap,dtype = int)
m.drawparallels(x_grid)
m.drawmeridians(y_grid)
plt.grid()
plt.xticks(x_grid,x_grid,fontsize = 18)
plt.yticks(y_grid,y_grid,fontsize = 18)
plt.xlabel('longitude: °E',fontsize = 20)
plt.ylabel('latitude: °N',fontsize = 20)
# plt.title(filename,fontsize = 20)
# plt.title(u'中国站点分布',fontsize = 20)
#画出海岸线和国境线
m.drawcoastlines()
m.drawcounties()
m.drawcountries()
plt.show()
return None
f3 = 'physic/CAPE/18080420.000'
physic_data = get_physic_data(f3,plot = True)
contourf_data_on_map(physic_data[2],physic_data[0],physic_data[1],gap=10)
读取数据
def get_EC_thin_physic_data(filename,plot = True,label_gap = 2):
'''
func:获取EC_thin/physic 或者 EC_thin路径下的物理量,默认EC_thin的数据是 等经纬网格的;
doc: 空间分辨率为 0.125*0.125 或者 0.25*0.25 ; 时间分辨率为3小时
physic类型的数据存储形式为:
第0行为时间信息
第1行为 经纬度网格信息,包括场数据的shape,
从第2行开始,现实中的每一行数据(纬向方向)由 9行组成;
#eg: shape为 81*81,每一行数据有81个数据;而在存储时,每10个数据占据一行,
# 剩下1个数据独占一行,因此 9行对应1行;
input:
filename: 文件名
plot: 默认True,绘制物理量场
label_gap: Plot中,x和y label的坐标经纬度间隔
return:
lon_grid : 场对应的经度信息
lat_grid : 场对应的纬度信息
rain_grid:场数值
以list形式返回
[lon_grid,lat_grid,rain_grid]
'''
f=open(filename,mode='r')
#此时每行的都为 字符格式
str_data = f.readlines()
data = []
for i in range(len(str_data)):
#将每行的字符类型的数字转为float型,如果某一行存在非数字字符,则跳过该行
try:
#依次读取每一行数据
line_data = str_data[i]
#如果该行为空(只有换行符),则跳过;否则,将其转换为数值型
if len(line_data)>1:
#去除换行符并将每个字符数据分开
line_data = line_data.strip().split()
#将数据类型由str转换为float型
line_data=[float(line_data[i]) for i in range(len(line_data))]
line_data = np.array(line_data).reshape(1,-1)
data.append(line_data)
except Exception as e:
print('第{:}行含有非数字字符 '.format(i))
print(e)
print()
pass
#第0行为时间信息
#第1行为 经纬度网格信息
# T = data[0]
loc_info = data[1]
det_lat = abs(loc_info[0,0])
det_lon = abs(loc_info[0,1])
lon_max = loc_info[0,3]
lon_min = loc_info[0,2]
lat_max = loc_info[0,4]
lat_min = loc_info[0,5]
lat_range = np.arange(lat_min,lat_max+det_lat,det_lat)
lon_range = np.arange(lon_min,lon_max+det_lon,det_lon)
lon_grid,lat_grid = np.meshgrid(lon_range,lat_range)
lat_grid = lat_grid[-1::-1,:]
#获取等经纬度场数据,并可视化;
#从第二行开始
length = data[2].shape[1]
#判断每一行存储的数据个数(一般是 10),
#而一般的场的shape是 81*81,因此需要 int(np.ceil(81/10)) = 9 行
#来表示真实场的一行数据
k = int(np.ceil(lat_grid.shape[1]/length))
tp = []
for i in range(lat_grid.shape[0]):
tp_line = []
for j in range(k):
tp_line = tp_line+list(data[i*k+2+j].ravel())
tp_line = np.array(tp_line).reshape(1,-1)
tp.append(tp_line)
tp = np.concatenate(tp,axis = 0)
#将降水场可视化出来
if plot:
plt.figure(figsize = (16,10))
cs = plt.contourf(tp[-1::-1],
# levels= np.arange(0,32+1,4),
extend='both',
cmap = plt.cm.rainbow) #extend参数使得colorbar两端变尖
#cs.cmap.set_over('red')
#cs.cmap.set_under('blue')
#cs.changed()
cb = plt.colorbar(cs)
cb.ax.tick_params(labelsize=16) #设置色标刻度字体大小。
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
font = {'family' : 'serif',
'color' : 'darkred',
'weight' : 'normal',
'size' : 20,
}
cb.set_label('',fontdict=font) #设置colorbar的标签字体及其大小
#确定横纵坐标轴的gap
label_gap = label_gap
plt.xticks(np.arange(0,len(lon_range),label_gap/det_lon), np.arange(lon_min,lon_max+1,label_gap,dtype = int),fontsize = 16)
plt.yticks(np.arange(0,len(lat_range),label_gap/det_lat), np.arange(lat_min,lat_max+1,label_gap,dtype = int),fontsize = 16)
plt.xlabel("longitude°E",fontsize = 20)
plt.ylabel("latitude°N",fontsize = 20)
plt.title(filename,fontsize = 20)
physic_info = [lon_grid,lat_grid,tp]
return physic_info
运行代码:
f4 = 'ecmwf_thin/T/1000/18080408.006'
ec_T = get_EC_thin_physic_data(f4,plot=True)
contourf_data_on_map(ec_T[2],ec_T[0],ec_T[1])
右边三个数组是左边列表的展开:依次为:等经度网格、等纬度网格、网格数值场
其他类型的micaps数据,读取方法大同小异。最重要的是理解这个数据是什么,有什么意义,怎么存储的!