对于地学研究中,常见的就是画带有地图的空间场,在python中常用的就是基于basemap(好像停止更新了,不太确定)和cartopy(英国气象局开发,越来越完善)进行绘制,本文基于basemap介绍一些入门:画空间场以及多子图。
几年前安装basemap是真的很麻烦,现在已经很简单了,直接一句话:
conda install basemap
import matplotlib as mpl from matplotlib.colors import LogNorm from mpl_toolkits.basemap import Basemap, cm, shiftgrid, addcyclic import matplotlib.pyplot as plt
这个函数可以直接使用,画每一个子图里面的空间场信息
def Plot2DForsubplots(data2D, lat, lon, DrawingNo, _reverse = True, defaultcamp = True):
'''
绘制 多子图空间场
----------
data2D: np.array, 2维变量数据 [lat,lon]
lat: np.array, 1维, data2D相应的纬度信息
lon: np.array, 1维, data2D相应的经度信息
DrawingNo: str, 标号信息
_reverse: 如果data2D的纬度是是从-90:90, 则需要将纬度反向, 变为90:-90
-------
'''
t0Plot2D= time.time()
print('*****************函数Plot2D开始*********************')
if _reverse:
data2D_plot = data2D[::-1, :] # 纬度反向 画图要90:-90
else:
data2D_plot = data2D# 纬度反向 画图要90:-90
# ========plot================
# plt.subplots(figsize=(12, 8))
mpl.rcParams['font.sans-serif'] = ['Times New Roman'] # 设置matplotlib整体用Times New Roman
mpl.rcParams['font.weight'] = 'bold' # 设置matplotlib整体用Times New Roman
mpl.rcParams['font.size'] = 14 # 设置matplotlib整体用Times New Roman
ax = plt.gca()
ax.yaxis.set_tick_params(labelsize=10.0)
map = Basemap(projection='cyl', llcrnrlat=np.min(lat), urcrnrlat=np.max(lat), resolution='c',
llcrnrlon=np.min(lon),
urcrnrlon=np.max(lon)) # 所需的地图域(度)的左下角的经度 llcrnrlat The lower left corner geographical longitude
# map.drawcoastlines(color='grey',linewidth=1)
map.drawparallels(np.arange(-90, 91., 30.), labels=[True, False, False, True], fontsize=8,
fontproperties='Times New Roman',
color='grey', linewidth=0.5) # 画平行线
map.drawmeridians(np.arange(0., 360, 60), labels=[True, False, False, True], fontsize=8,
fontproperties='Times New Roman',
color='grey', linewidth=0.5) # 在地图上画经线
# map.readshapefile('/newshps/country1', 'country',
# color='grey', linewidth=1)
#map.fillcontinents(color='silver', zorder=1)
map.etopo() # 浮雕
map.drawcoastlines()
# ===========自定义color===============
if defaultcamp:
norm = mpl.colors.Normalize(vmin=-40, vmax=40)
cax = plt.imshow(data2D_plot, cmap='seismic', extent=[np.min(lon), np.max(lon), np.min(lat), np.max(lat)], norm=norm,
alpha=1) # clim:色标范围 extent:画图经纬度区域 zorder:控制绘图顺序 vmin=data2D.max(), vmax=data2D.min(),
else:
#######==========自己设置colorbar间隔====================
# mycolors = [(0.52941, 0.7, 1), (0.52941, 0.80784, 0.92157), (0.95,0.55,0.55), (0.98039, 0.50196, 0.44706), (0.99, 0.38824, 0.3),
# (1, 0.28, 0.28), (1, 0.15, 0.15), (1, 0, 0)]
# my_cmap = mpl.colors.ListedColormap(mycolors)
# clevs = [-12,-8,-6, -4, -2, 0, 2, 4, 6, 8, 10, 12]
# print(my_cmap.N)
# norm = mpl.colors.BoundaryNorm(clevs, my_cmap.N)
# cax = plt.imshow(data2D_plot, cmap=my_cmap,
# extent=[np.min(lon), np.max(lon), np.min(lat), np.max(lat)], norm=norm,
# alpha=1) # clim:色标范围 extent:画图经纬度区域 zorder:控制绘图顺序
norm = mpl.colors.Normalize(vmin=-12, vmax=12)
cax = plt.imshow(data2D_plot, cmap='seismic', extent=[np.min(lon), np.max(lon), np.min(lat), np.max(lat)], norm=norm, alpha=1) # clim:色标范围 extent:画图经纬度区域 zorder:控制绘图顺序
# =======添加图号===============
plt.text(0, 91.5, DrawingNo, fontsize=12, fontproperties='Times New Roman')
#plt.title(titles, fontsize=15, fontproperties='Times New Roman')
# =======设置colorbar===============
# cax = plt.axes([0.35, 0.28, 0.4, 0.02]) #前两个指的是相对于坐标原点的位置,后两个指的是坐标轴的长/宽度 这个设置的colorbar位置
cbar = plt.colorbar()
cbar.set_label('Temperature', fontsize=12, fontproperties='Times New Roman')
ax.spines['bottom'].set_linewidth(2); ###设置底部坐标轴的粗细
ax.spines['left'].set_linewidth(2); ####设置左边坐标轴的粗细
ax.spines['right'].set_linewidth(2); ###设置右边坐标轴的粗细
ax.spines['top'].set_linewidth(2); ###设置右边坐标轴的粗细
print('***********************函数Plot2D结束, 耗时: %.3f s / %.3f mins****************' % ((time.time() - t0Plot2D), (time.time() - t0Plot2D) / 60))
这个脚本是调用不断调用Plot2DForsubplots来实现画多子图,如果不需要画多子图,把循环去掉就行。
import os
import time
import code
import numpy as np
import pandas as pd
import xarray as xr
#=画图需要的包
import matplotlib as mpl
from matplotlib.colors import LogNorm
from mpl_toolkits.basemap import Basemap, cm, shiftgrid, addcyclic
import matplotlib.pyplot as plt
def Plot2DForsubplots(data2D, lat, lon, DrawingNo, _reverse = True, defaultcamp = True):
'''
绘制 多子图空间场
----------
data2D: np.array, 2维变量数据 [lat,lon]
lat: np.array, 1维, data2D相应的纬度信息
lon: np.array, 1维, data2D相应的经度信息
DrawingNo: str, 标号信息
_reverse: 如果data2D的纬度是是从-90:90, 则需要将纬度反向, 变为90:-90
-------
'''
t0Plot2D= time.time()
print('*****************函数Plot2D开始*********************')
if _reverse:
data2D_plot = data2D[::-1, :] # 纬度反向 画图要90:-90
else:
data2D_plot = data2D# 纬度反向 画图要90:-90
# ========plot================
# plt.subplots(figsize=(12, 8))
mpl.rcParams['font.sans-serif'] = ['Times New Roman'] # 设置matplotlib整体用Times New Roman
mpl.rcParams['font.weight'] = 'bold' # 设置matplotlib整体用Times New Roman
mpl.rcParams['font.size'] = 14 # 设置matplotlib整体用Times New Roman
ax = plt.gca()
ax.yaxis.set_tick_params(labelsize=10.0)
map = Basemap(projection='cyl', llcrnrlat=np.min(lat), urcrnrlat=np.max(lat), resolution='c',
llcrnrlon=np.min(lon),
urcrnrlon=np.max(lon)) # 所需的地图域(度)的左下角的经度 llcrnrlat The lower left corner geographical longitude
# map.drawcoastlines(color='grey',linewidth=1)
map.drawparallels(np.arange(-90, 91., 30.), labels=[True, False, False, True], fontsize=8,
fontproperties='Times New Roman',
color='grey', linewidth=0.5) # 画平行线
map.drawmeridians(np.arange(0., 360, 60), labels=[True, False, False, True], fontsize=8,
fontproperties='Times New Roman',
color='grey', linewidth=0.5) # 在地图上画经线
# map.readshapefile('/home/tongxuan/newshps/country1', 'country',
# color='grey', linewidth=1)
#map.fillcontinents(color='silver', zorder=1)
map.etopo() # 浮雕
map.drawcoastlines()
# ===========自定义color===============
if defaultcamp:
norm = mpl.colors.Normalize(vmin=-40, vmax=40)
cax = plt.imshow(data2D_plot, cmap='seismic', extent=[np.min(lon), np.max(lon), np.min(lat), np.max(lat)], norm=norm,
alpha=1) # clim:色标范围 extent:画图经纬度区域 zorder:控制绘图顺序 vmin=data2D.max(), vmax=data2D.min(),
else:
#######==========自己设置colorbar间隔====================
# mycolors = [(0.52941, 0.7, 1), (0.52941, 0.80784, 0.92157), (0.95,0.55,0.55), (0.98039, 0.50196, 0.44706), (0.99, 0.38824, 0.3),
# (1, 0.28, 0.28), (1, 0.15, 0.15), (1, 0, 0)]
# my_cmap = mpl.colors.ListedColormap(mycolors)
# clevs = [-12,-8,-6, -4, -2, 0, 2, 4, 6, 8, 10, 12]
# print(my_cmap.N)
# norm = mpl.colors.BoundaryNorm(clevs, my_cmap.N)
# cax = plt.imshow(data2D_plot, cmap=my_cmap,
# extent=[np.min(lon), np.max(lon), np.min(lat), np.max(lat)], norm=norm,
# alpha=1) # clim:色标范围 extent:画图经纬度区域 zorder:控制绘图顺序
norm = mpl.colors.Normalize(vmin=-12, vmax=12)
cax = plt.imshow(data2D_plot, cmap='seismic', extent=[np.min(lon), np.max(lon), np.min(lat), np.max(lat)], norm=norm, alpha=1) # clim:色标范围 extent:画图经纬度区域 zorder:控制绘图顺序
# =======添加图号===============
plt.text(0, 91.5, DrawingNo, fontsize=12, fontproperties='Times New Roman')
#plt.title(titles, fontsize=15, fontproperties='Times New Roman')
# =======设置colorbar===============
# cax = plt.axes([0.35, 0.28, 0.4, 0.02]) #前两个指的是相对于坐标原点的位置,后两个指的是坐标轴的长/宽度 这个设置的colorbar位置
cbar = plt.colorbar()
cbar.set_label('Temperature', fontsize=12, fontproperties='Times New Roman')
ax.spines['bottom'].set_linewidth(2); ###设置底部坐标轴的粗细
ax.spines['left'].set_linewidth(2); ####设置左边坐标轴的粗细
ax.spines['right'].set_linewidth(2); ###设置右边坐标轴的粗细
ax.spines['top'].set_linewidth(2); ###设置右边坐标轴的粗细
print('***********************函数Plot2D结束, 耗时: %.3f s / %.3f mins****************' % ((time.time() - t0Plot2D), (time.time() - t0Plot2D) / 60))
def PlotTenYearAve():
'''
画CEMS2(第二版数据)和ERA5逐十年平均 多子图
:return:
'''
#=加载数据
Cesm2DataAnnual = np.load('/home/Cesm2DataAnnual.npy')#1950-2014 (65, 181, 359) lat:-90:90 lon:0:360
Era5DataAnnual = np.load('/home/ERA5DataAnnual.npy')#1950-2014 (65, 181,360) lat:-90:90 lon:0:360
LonLR = np.arange(0, 358.75, 1)
LatLR = np.arange(-90, 90.5, 1)
YearInterval = 10 #每十年画一张图
DrawNo = ['(a)', '(b)', '(c)',
'(d)', '(e)', '(f)',
'(g)', '(h)', '(i)',
'(j)', '(k)', '(l)',
'(m)', '(n)', '(o)',
'(p)', '(q)', '(i)']
fig = plt.figure(figsize=(25, 18))
fig.subplots_adjust(wspace=0.05)
for i in range((2014-1950)//10):
Yeartitles = str(1950 + i * 10) + 's'
Cesm2DataEach10Year = np.mean(Cesm2DataAnnual[i*YearInterval : (i+1)*YearInterval, :, :], axis=0) # (181, 359) lat:-90:90 lon:0:358
Era5DataEach10Year_ = np.mean(Era5DataAnnual[i * YearInterval: (i + 1) * YearInterval, :, :], axis=0)#(181,360) lat:-90:90 lon:0:360
Era5DataEach10Year = Era5DataEach10Year_[:Cesm2DataEach10Year.shape[0], :Cesm2DataEach10Year.shape[1]]# (181, 359)
Cesm2_Era5Each10Year = Cesm2DataEach10Year - Era5DataEach10Year
plt.subplot((2014-1950)//10, 3, i * 3 + 1)
Plot2DForsubplots(Cesm2DataEach10Year - 273.15, LatLR, LonLR, '%s CESM2 %s' % (DrawNo[i * 3 + 0],Yeartitles), _reverse=True, defaultcamp=True)
plt.subplot((2014 - 1950) // 10, 3, i * 3 + 2)
Plot2DForsubplots(Era5DataEach10Year - 273.15, LatLR, LonLR, '%s ERA5 %s' % (DrawNo[i * 3 + 1],Yeartitles), _reverse=True, defaultcamp=True)
plt.subplot((2014 - 1950) // 10, 3, i * 3 + 3)
Plot2DForsubplots(Cesm2_Era5Each10Year, LatLR, LonLR, '%s Difference %s' % (DrawNo[i * 3 + 2],Yeartitles), _reverse=True, defaultcamp=False)
plt.savefig('/homeTenYearAve.jpg', dpi=600, bbox_inches='tight')
if __name__ == "__main__":
t0 = time.time()
# ===================================================================================
print('*****************程序开始*********************' )
PlotTenYearAve()
print('***********************程序结束, 耗时: %.3f s / %.3f mins****************' % ((time.time() - t0), (time.time() - t0) / 60))
code.interact(local=locals())